Clases OOP: Métodos estáticos vs de instancia, atributos y relaciones
Este artículo es una guía exhaustiva de los componentes de clases OOP, incluyendo elementos estáticos e de instancia, relaciones y genéricos con ejemplos prácticos.
Resumen rápido
Las clases se componen de atributos, métodos, visibilidades y contratos. Los elementos estáticos pertenecen a la clase en su conjunto, mientras que los elementos de instancia pertenecen a objetos individuales. Los genéricos aumentan la seguridad de tipos y la reutilización de código.
Descripción técnica compacta
Una clase define estructura y comportamiento a través de atributos, métodos, visibilidades, constructores, invariantes y contratos.
Elementos estáticos vs de instancia:
Elementos estáticos (a nivel de clase)
- Atributos estáticos: Pertenecen a la clase, no a los objetos
- Métodos estáticos: Se pueden invocar sin crear un objeto
- Uso: Contadores, cachés, fábricas, constantes
- Acceso: A través del nombre de la clase, no del objeto
Elementos de instancia (a nivel de objeto)
- Atributos de instancia: Cada objeto tiene su propia copia
- Métodos de instancia: Operan sobre el estado del objeto
- Uso: Datos y comportamiento específicos del objeto
- Acceso: Solo a través de la instancia del objeto
Relaciones entre clases (UML):
- Asociación: Relación simple entre clases
- Agregación: Relación “tiene-un” con partes independientes
- Composición: Relación “es-parte-de” con partes dependientes
- Especialización: Una subclase hereda de una clase base
Genéricos:
- Seguridad de tipos: Verificación de tipos en tiempo de compilación
- Reutilización: Un código para diferentes tipos
- Contenedores:
List<T>,Map<K,V>,std::vector<T>
Puntos clave para el examen
- Atributos estáticos: Pertenecen a la clase, valor compartido por todos los objetos
- Atributos de instancia: Cada objeto tiene su propia copia
- Métodos estáticos: Se invocan sin objeto, no tienen puntero this
- Métodos de instancia: Operan sobre el estado del objeto con puntero this
- Relaciones entre clases: Asociación, agregación, composición, herencia
- Notación UML: Símbolos diferentes para tipos de relaciones
- Genéricos: Plantillas de tipo para contenedores con seguridad de tipos
- Relevancia en exámenes: Comprensión fundamental para desarrollo OOP
Componentes principales
- Atributos estáticos: Variables de clase para estado compartido
- Atributos de instancia: Variables de objeto para estado individual
- Métodos estáticos: Métodos de clase sin referencia a objetos
- Métodos de instancia: Métodos de objeto con acceso al estado
- Constructores: Inicialización de objetos
- Relaciones: Conexiones estructurales entre clases
- Genéricos: Clases y métodos con parámetros de tipo
- UML: Notación gráfica para diseño de clases
Ejemplos prácticos
1. Elementos estáticos vs de instancia en Java
public class Mitarbeiter {
// Atributos estáticos (pertenecen a la clase)
private static int anzahlMitarbeiter = 0;
private static final String FIRMA = "TechCorp";
private static double mindestgehalt = 2000.0;
// Atributos de instancia (pertenecen al objeto)
private int mitarbeiterId;
private String name;
private double gehalt;
// Bloque de inicialización estático
static {
System.out.println("Mitarbeiter-Klasse wird geladen");
anzahlMitarbeiter = 0;
}
// Constructor (inicialización de instancia)
public Mitarbeiter(String name, double gehalt) {
this.mitarbeiterId = ++anzahlMitarbeiter;
this.name = name;
this.gehalt = Math.max(gehalt, mindestgehalt);
System.out.println("Mitarbeiter " + name + " erstellt (ID: " + mitarbeiterId + ")");
}
// Método estático (se puede invocar sin objeto)
public static int getAnzahlMitarbeiter() {
return anzahlMitarbeiter;
}
public static String getFirma() {
return FIRMA;
}
public static void setMindestgehalt(double mindestgehalt) {
if (mindestgehalt > 0) {
Mitarbeiter.mindestgehalt = mindestgehalt;
}
}
// Método de instancia (requiere objeto)
public void erhoeheGehalt(double prozent) {
this.gehalt *= (1 + prozent / 100);
System.out.println(name + "s Gehalt erhöht auf " + gehalt);
}
public void anzeigen() {
System.out.println("ID: " + mitarbeiterId + ", Name: " + name +
", Gehalt: " + gehalt + ", Firma: " + FIRMA);
}
// Getters/Setters para atributos de instancia
public String getName() {
return name;
}
public double getGehalt() {
return gehalt;
}
}
// Uso de la clase
public class MitarbeiterDemo {
public static void main(String[] args) {
// Invocar métodos estáticos (sin objeto)
System.out.println("Anzahl Mitarbeiter: " + Mitarbeiter.getAnzahlMitarbeiter());
System.out.println("Firma: " + Mitarbeiter.getFirma());
Mitarbeiter.setMindestgehalt(2500.0);
// Crear objetos (instancias)
Mitarbeiter alice = new Mitarbeiter("Alice", 3000.0);
Mitarbeiter bob = new Mitarbeiter("Bob", 2800.0);
// Invocar métodos de instancia
alice.erhoeheGehalt(5.0);
bob.anzeigen();
// Método estático después de crear objetos
System.out.println("Anzahl Mitarbeiter: " + Mitarbeiter.getAnzahlMitarbeiter());
// Error: this no está disponible en método estático
// public static void fehlerhafteMethode() {
// System.out.println(this.name); // Fehler: kann nicht auf this zugreifen
// }
}
}
2. Relaciones entre clases con ejemplos UML
// Asociación: Docente imparte Cursos
public class Dozent {
private String name;
private List<Kurs> unterrichteteKurse = new ArrayList<>();
public Dozent(String name) {
this.name = name;
}
public void addKurs(Kurs kurs) {
unterrichteteKurse.add(kurs);
kurs.setDozent(this); // Referencia inversa
}
public void zeigeKurse() {
System.out.println(name + " unterrichtet:");
for (Kurs kurs : unterrichteteKurse) {
System.out.println(" - " + kurs.getTitel());
}
}
}
public class Kurs {
private String titel;
private Dozent dozent; // Referencia inversa
public Kurs(String titel) {
this.titel = titel;
}
public void setDozent(Dozent dozent) {
this.dozent = dozent;
}
public String getTitel() {
return titel;
}
}
// Agregación: Departamento tiene Empleados (pueden existir sin el departamento)
public class Abteilung {
private String name;
private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
public Abteilung(String name) {
this.name = name;
}
public void addMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.add(mitarbeiter);
}
public void removeMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.remove(mitarbeiter);
// Mitarbeiter sigue existiendo
}
}
// Composición: Pedido tiene Líneas de Pedido (existen solo con el pedido)
public class Bestellung {
private String bestellId;
private List<Bestellposition> positionen = new ArrayList<>();
public Bestellung(String bestellId) {
this.bestellId = bestellId;
}
public void addPosition(String produkt, int menge, double preis) {
Bestellposition position = new Bestellposition(produkt, menge, preis);
positionen.add(position);
}
public double berechneGesamtbetrag() {
return positionen.stream()
.mapToDouble(Bestellposition::getGesamtpreis)
.sum();
}
// Clase interna para composición
private class Bestellposition {
private String produkt;
private int menge;
private double einzelpreis;
public Bestellposition(String produkt, int menge, double einzelpreis) {
this.produkt = produkt;
this.menge = menge;
this.einzelpreis = einzelpreis;
}
public double getGesamtpreis() {
return menge * einzelpreis;
}
}
}
3. Genéricos con elementos estáticos
// Clase genérica con elementos estáticos
public class Container<T> {
// Atributos estáticos (¡no son genéricos!)
private static int anzahlContainer = 0;
private static final String VERSION = "1.0";
// Atributos de instancia (son genéricos)
private T inhalt;
private int id;
public Container(T inhalt) {
this.inhalt = inhalt;
this.id = ++anzahlContainer;
}
// Método estático (no puede acceder a T)
public static int getAnzahlContainer() {
return anzahlContainer;
}
public static String getVersion() {
return VERSION;
}
// Método de instancia (puede acceder a T)
public T getInhalt() {
return inhalt;
}
public void setInhalt(T inhalt) {
this.inhalt = inhalt;
}
public void anzeigen() {
System.out.println("Container #" + id + ": " +
(inhalt != null ? inhalt.toString() : "leer"));
}
// Método genérico (estático)
public static <U> Container<U> create(U inhalt) {
return new Container<>(inhalt);
}
}
// Uso
public class ContainerDemo {
public static void main(String[] args) {
// Llamar métodos estáticos
System.out.println("Container-Version: " + Container.getVersion());
// Diferentes tipos de contenedor
Container<String> stringContainer = new Container<>("Hallo");
Container<Integer> intContainer = new Container<>(42);
Container<Double> doubleContainer = Container.create(3.14);
stringContainer.anzeigen();
intContainer.anzeigen();
doubleContainer.anzeigen();
System.out.println("Anzahl Container: " + Container.getAnzahlContainer());
// Error: Los atributos estáticos no son genéricos
// Container<String>.getAnzahlContainer(); // Error de sintaxis
}
}
4. Factory Pattern con métodos estáticos
public class FahrzeugFactory {
// Métodos factory estáticos
public static Fahrzeug erstellePKW(String marke, int leistung) {
return new PKW(marke, leistung, 4);
}
public static Fahrzeug erstelleMotorrad(String marke, int leistung) {
return new Motorrad(marke, leistung, false);
}
public static Fahrzeug erstelleLKW(String marke, int leistung, double ladung) {
return new LKW(marke, leistung, ladung);
}
// Método estático con validación
public static Fahrzeug erstelleFahrzeug(String typ, String marke, int leistung) {
switch (typ.toLowerCase()) {
case "pkw":
return erstellePKW(marke, leistung);
case "motorrad":
return erstelleMotorrad(marke, leistung);
case "lkw":
return erstelleLKW(marke, leistung, 1000.0);
default:
throw new IllegalArgumentException("Unbekannter Fahrzeugtyp: " + typ);
}
}
}
// Clase base abstracta
abstract class Fahrzeug {
protected String marke;
protected int leistung;
public Fahrzeug(String marke, int leistung) {
this.marke = marke;
this.leistung = leistung;
}
public abstract void anzeigen();
}
// Clases concretas
class PKW extends Fahrzeug {
private int anzahlTueren;
public PKW(String marke, int leistung, int anzahlTueren) {
super(marke, leistung);
this.anzahlTueren = anzahlTueren;
}
@Override
public void anzeigen() {
System.out.println("PKW: " + marke + ", " + leistung + " PS, " + anzahlTueren + " Türen");
}
}
class Motorrad extends Fahrzeug {
private boolean hatSeitenwagen;
public Motorrad(String marke, int leistung, boolean hatSeitenwagen) {
super(marke, leistung);
this.hatSeitenwagen = hatSeitenwagen;
}
@Override
public void anzeigen() {
System.out.println("Motorrad: " + marke + ", " + leistung + " PS, " +
(hatSeitenwagen ? "mit" : "ohne") + " Seitenwagen");
}
}
class LKW extends Fahrzeug {
private double ladung;
public LKW(String marke, int leistung, double ladung) {
super(marke, leistung);
this.ladung = ladung;
}
@Override
public void anzeigen() {
System.out.println("LKW: " + marke + ", " + leistung + " PS, " + ladung + " kg Ladung");
}
}
// Uso de la factory
public class FactoryDemo {
public static void main(String[] args) {
// Usar métodos factory estáticos
Fahrzeug golf = FahrzeugFactory.erstellePKW("Volkswagen", 110);
Fahrzeug harley = FahrzeugFactory.erstelleMotorrad("Harley", 80);
Fahrzeug scania = FahrzeugFactory.erstelleLKW("Scania", 500, 20000.0);
golf.anzeigen();
harley.anzeigen();
scania.anzeigen();
// Creación dinámica
Fahrzeug bmw = FahrzeugFactory.erstelleFahrzeug("pkw", "BMW", 150);
bmw.anzeigen();
}
}
Notación UML para componentes de clase
Clase con elementos estáticos e instancia
+---------------------------+
| Mitarbeiter |
+---------------------------+
| - anzahlMitarbeiter: int | <<static>>
| - FIRMA: String | <<static>>
| - mitarbeiterId: int |
| - name: String |
| - gehalt: double |
+---------------------------+
| + getAnzahlMitarbeiter(): int | <<static>>
| + setMindestgehalt(double): void | <<static>>
| + erhoeheGehalt(double): void |
| + anzeigen(): void |
+---------------------------+
Relaciones en UML
Dozent 1..* --* Kurs (Asociación)
Abteilung 1 --o* Mitarbeiter (Agregación)
Bestellung 1 --* Bestellposition (Composición)
Fahrzeug <|-- PKW (Herencia)
Estático vs. Instancia: Guía de decisión
Cuándo usar elementos estáticos
Atributos estáticos:
- Contadores para todas las instancias
- Constantes para toda la clase
- Recursos compartidos, como conexiones a bases de datos
- Cachés para la clase
Métodos estáticos:
- Métodos factory para crear objetos
- Métodos de utilidad sin estado
- Métodos de conversión
- Métodos de validación
Cuándo usar elementos de instancia
Atributos de instancia:
- Datos específicos del objeto
- Estado que cambia
- Configuración por objeto
Métodos de instancia:
- Métodos que acceden al estado del objeto
- Comportamiento que depende de datos de instancia
- Métodos que necesitan el puntero this
Ventajas y desventajas
Ventajas de elementos estáticos
- Eficiencia de memoria: Solo una copia para todos los objetos
- Acceso simple: Llamable sin crear instancia
- Estado compartido: Igual para todos los objetos
- Factory Pattern: Creación sencilla de objetos
Desventajas
- Estado global: Dificulta las pruebas
- Thread-Safety: Riesgos con acceso concurrente
- Flexibilidad: Sin polimorfismo
- Inicialización: Dependencias complejas
Preguntas frecuentes de examen
-
¿Cuál es la diferencia entre atributos estáticos e instancia? Los atributos estáticos pertenecen a la clase (una sola copia), los atributos de instancia pertenecen al objeto (una copia por objeto).
-
¿Pueden los métodos estáticos acceder a atributos de instancia? No, porque no tienen un puntero this y no saben a qué objeto pertenecen.
-
¡Explica agregación vs composición! Agregación: Las partes pueden existir sin el todo. Composición: Las partes solo existen con el todo.
-
¿Por qué los atributos estáticos no son genéricos? Pertenecen a la clase, no a la instancia, por lo que existe solo una versión por clase.
Fuentes principales
- https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members
- https://www.uml-diagrams.org/class-diagrams.html
Continúa en la ruta de aprendizaje OOP
Todos los artículos de OOP están completos. Regresa al primer artículo: Objektorientierte Programmierung OOP Grundlagen.



