Las interfaces y clases abstractas son los mecanismos de Java para definir contratos y abstracciones. Con las mejoras introducidas hasta Java 21, las interfaces son mucho más potentes que en las primeras versiones del lenguaje.
Clases abstractas
Una clase abstracta no se puede instanciar directamente. Define el esqueleto de una familia de clases, proporcionando implementación parcial que las subclases concretan:
public abstract class Figura {
protected String color;
public Figura(String color) {
this.color = color;
}
// Método abstracto: cada figura implementa el suyo
public abstract double calcularArea();
// Método concreto: compartido por todas las figuras
public void describir() {
System.out.printf("Figura %s con área %.2f%n", color, calcularArea());
}
}
public class Circulo extends Figura {
private double radio;
public Circulo(String color, double radio) {
super(color);
this.radio = radio;
}
@Override
public double calcularArea() {
return Math.PI * radio * radio;
}
}
public class Rectangulo extends Figura {
private double ancho, alto;
public Rectangulo(String color, double ancho, double alto) {
super(color);
this.ancho = ancho;
this.alto = alto;
}
@Override
public double calcularArea() {
return ancho * alto;
}
}
Interfaces
Una interfaz define un contrato: un conjunto de métodos que las clases que la implementan deben proporcionar. A diferencia de la herencia (una sola clase padre), una clase puede implementar múltiples interfaces:
public interface Serializable {
byte[] serializar();
}
public interface Comparable {
int compareTo(T otro);
}
public class Producto implements Comparable, Serializable {
private String nombre;
private double precio;
public Producto(String nombre, double precio) {
this.nombre = nombre;
this.precio = precio;
}
@Override
public int compareTo(Producto otro) {
return Double.compare(this.precio, otro.precio);
}
@Override
public byte[] serializar() {
return nombre.getBytes();
}
}
Métodos default en interfaces (Java 8+)
Desde Java 8, las interfaces pueden tener métodos con implementación por defecto (default). Esto permite añadir métodos a interfaces existentes sin romper las implementaciones:
public interface Coleccion{ void agregar(T elemento); int tamanyo(); default boolean estaVacia() { return tamanyo() == 0; } default void mostrar() { System.out.println("Colección con " + tamanyo() + " elementos"); } }
Interfaces funcionales (Java 8+)
Una interfaz con un único método abstracto se llama interfaz funcional. Java las marca con @FunctionalInterface. Son la base de las lambdas:
@FunctionalInterface public interface Transformador{ R transformar(T entrada); } // Uso con lambda Transformador longitud = s -> s.length(); System.out.println(longitud.transformar("Hola")); // 4 // O con referencia a método Transformador mayusculas = String::toUpperCase; System.out.println(mayusculas.transformar("hola")); // HOLA
Cuándo usar interfaz vs. clase abstracta
Criterio | Interfaz | Clase abstracta |
Herencia múltiple | Sí (una clase puede implementar muchas) | No (solo una clase padre) |
Estado (atributos) | Solo constantes ( | Sí, atributos de instancia |
Constructor | No | Sí |
Uso típico | Contrato de capacidad («puede hacer X») | Familia de clases con código compartido |
Regla práctica en Java moderno: prefiere interfaces. Úsalas para definir contratos. Reserva las clases abstractas para cuando realmente necesites compartir estado o lógica de inicialización entre las subclases.
