5. Programación orientada a objetos¶
5.1 Clases y objetos¶
Una clase es un molde que describe cómo son y qué hacen un tipo de objetos. Un objeto es un ejemplar concreto creado a partir de ese molde.
| Clase | Objetos |
|---|---|
Alumno (nombre, nota) |
Ana con un 8, Luis con un 6 |
CuentaBancaria (titular, saldo) |
La cuenta de Ana con 300 € |
Una clase tiene:
- Atributos: los datos que guarda cada objeto (su estado).
- Constructores: el código que inicializa un objeto al crearlo con
new. - Métodos: lo que el objeto sabe hacer (su comportamiento).
public class Alumno {
// atributos
private String nombre;
private double nota;
// constructor
public Alumno(String nombre, double nota) {
this.nombre = nombre; // this.nombre es el atributo; nombre, el parámetro
this.nota = nota;
}
// métodos
public boolean estaAprobado() {
return nota >= 5;
}
public String getNombre() { return nombre; }
public double getNota() { return nota; }
@Override
public String toString() {
return nombre + " (" + nota + ")";
}
}
Alumno a = new Alumno("Ana", 8.5); // new reserva memoria y llama al constructor
System.out.println(a.estaAprobado()); // true
System.out.println(a); // Ana (8.5): println llama a toString()
Variables que guardan referencias
a no contiene el objeto: contiene una referencia a él. Por eso Alumno b = a; no crea otro alumno; a y b apuntan al mismo. Si una referencia vale null y llamas a un método, obtienes NullPointerException.
Constructores¶
- Si no escribes ninguno, Java crea uno por defecto sin parámetros. Si escribes uno, el de por defecto desaparece.
- Puede haber varios (sobrecarga). Uno puede llamar a otro con
this(...), que debe ser la primera instrucción:
5.2 Encapsulación¶
La encapsulación consiste en ocultar los atributos (private) y permitir el acceso solo a través de métodos que controlan su uso. Así el objeto nunca queda en un estado inválido.
| Modificador | Accesible desde |
|---|---|
private |
Solo la propia clase. |
| (sin modificador) | Las clases del mismo paquete. |
protected |
El mismo paquete y las subclases. |
public |
Cualquier clase. |
public void setNota(double nota) {
if (nota < 0 || nota > 10) {
throw new IllegalArgumentException("Nota fuera de rango: " + nota);
}
this.nota = nota;
}
Regla general: atributos private, métodos public solo para lo que deba usarse desde fuera, y setters únicamente cuando tenga sentido cambiar el valor.
5.3 Miembros static¶
Un miembro static pertenece a la clase, no a cada objeto. Hay una sola copia compartida por todos.
public class Ticket {
private static int contador = 0; // compartido por todos los tickets
public static final int MAX = 999; // constante de clase
private final int numero; // propio de cada ticket
public Ticket() {
contador++;
numero = contador;
}
public static int getEmitidos() { return contador; }
}
new Ticket(); new Ticket();
System.out.println(Ticket.getEmitidos()); // 2: se llama con el nombre de la clase
- Un método
staticno puede usar atributos de instancia nithis, porque no se ejecuta sobre ningún objeto. - Las clases de utilidad (como
MathoArrays) solo tienen métodosstaticy un constructorprivatepara que nadie las instancie.
static y concurrencia
En el Tema 2 verás que un atributo static compartido por varios hilos puede dar resultados incorrectos si no se sincroniza. Tenlo en mente.
5.4 Herencia¶
La herencia permite crear una clase nueva (subclase) a partir de otra (superclase), reutilizando sus atributos y métodos y añadiendo o modificando lo necesario. Expresa una relación «es un»: un Gerente es un Empleado.
public class Empleado {
protected String nombre;
protected double sueldoBase;
public Empleado(String nombre, double sueldoBase) {
this.nombre = nombre;
this.sueldoBase = sueldoBase;
}
public double sueldo() { return sueldoBase; }
}
public class Gerente extends Empleado {
private double plus;
public Gerente(String nombre, double sueldoBase, double plus) {
super(nombre, sueldoBase); // llama al constructor de Empleado: primera línea
this.plus = plus;
}
@Override
public double sueldo() {
return super.sueldo() + plus; // reutiliza el método del padre
}
public double getPlus() { return plus; }
}
| Concepto | Significado |
|---|---|
extends |
Indica la superclase. Java solo permite una. |
super(...) |
Llama al constructor del padre. Si no lo pones, se llama a super() sin parámetros. |
super.metodo() |
Llama a la versión del padre de un método sobrescrito. |
@Override |
Indica que sobrescribes un método. El compilador comprueba que exista en el padre. |
Object |
Toda clase hereda, directa o indirectamente, de Object (toString, equals, hashCode…). |
final |
Una clase final no se puede heredar; un método final no se puede sobrescribir. |
5.5 Polimorfismo¶
Una variable del tipo de la superclase puede guardar cualquier objeto de sus subclases. Al llamar a un método, se ejecuta la versión del objeto real, no la del tipo de la variable:
Empleado[] plantilla = {
new Empleado("Eva", 1500),
new Gerente("Juan", 2000, 500)
};
for (Empleado e : plantilla) {
System.out.println(e.nombre + ": " + e.sueldo()); // 1500.0 y 2500.0
}
Para saber el tipo real se usa instanceof. Desde Java 16 puede declarar a la vez una variable del tipo comprobado (pattern matching):
5.6 Clases abstractas e interfaces¶
Clase abstracta¶
Una clase abstract no se puede instanciar. Sirve de base común y puede tener métodos abstract (sin cuerpo) que las subclases están obligadas a implementar:
public abstract class Figura {
public abstract double area(); // cada figura la calcula a su manera
public String descripcion() { // método normal, heredado
return getClass().getSimpleName() + " de área " + String.format("%.2f", area());
}
}
public class Circulo extends Figura {
private final double radio;
public Circulo(double radio) { this.radio = radio; }
@Override public double area() { return Math.PI * radio * radio; }
}
Interfaz¶
Una interfaz define un contrato: un conjunto de métodos que una clase se compromete a ofrecer. Una clase puede implementar varias interfaces.
public interface Redimensionable {
void escalar(double factor); // abstracto por defecto
default void duplicar() { escalar(2); } // método con implementación (Java 8+)
}
public class Rectangulo extends Figura implements Redimensionable, Comparable<Rectangulo> {
private double base, altura;
public Rectangulo(double base, double altura) { this.base = base; this.altura = altura; }
@Override public double area() { return base * altura; }
@Override public void escalar(double f) { base *= f; altura *= f; }
@Override public int compareTo(Rectangulo otro) { return Double.compare(area(), otro.area()); }
}
| Clase abstracta | Interfaz | |
|---|---|---|
| Relación | «es un» (jerarquía) | «sabe hacer» (capacidad) |
| Herencia | Solo una (extends) |
Varias (implements) |
| Atributos | Puede tener estado | Solo constantes |
| Constructores | Sí | No |
Interfaces que aparecen en PSP
Runnable (Tema 2: la tarea que ejecuta un hilo), Comparable y Comparator (ordenar), AutoCloseable (try con recursos). Todas son interfaces de un solo método, así que se pueden escribir con lambdas (apartado 7).
5.7 equals y hashCode¶
== compara referencias. Para que dos objetos con los mismos datos se consideren iguales hay que sobrescribir equals, y siempre que se sobrescriba equals hay que sobrescribir también hashCode (lo usan HashSet y HashMap):
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Alumno otro)) return false;
return nombre.equals(otro.nombre);
}
@Override
public int hashCode() {
return java.util.Objects.hash(nombre);
}
Los record (apartado 7) generan equals, hashCode y toString automáticamente.
Preguntas de repaso¶
- ¿Qué diferencia hay entre una clase y un objeto?
- ¿Por qué los atributos suelen ser
private? - Si una clase tiene un atributo
static int total, ¿cuántas copias hay con 10 objetos creados? - ¿Qué hace
super(...)y dónde debe ir? - Con
Empleado e = new Gerente(...), ¿qué versión desueldo()se ejecuta? - ¿Cuándo usarías una interfaz en lugar de una clase abstracta?
Practica