</>waridocu

Java

Interfaces: contratos entre clases

Qué es una interfaz, en qué se diferencia de una clase abstracta, por qué una clase puede implementar varias, y cómo se usan las interfaces de la biblioteca estándar.

javainterfacesimplementsabstractpoo

El problema: cosas que comparten una capacidad, no un origen

Una Factura, una Cancion y un Alumno no tienen nada que ver entre sí: no comparten atributos, no tienen un padre común razonable, y forzar un extends entre ellas sería inventar un parentesco falso. Y sin embargo las tres pueden necesitar la misma capacidad: por ejemplo, saber exportarse a texto para guardarlas en un archivo.

Con herencia no se puede resolver: Java permite extends una sola vez, y en cualquier caso una Cancion no es un Alumno. Lo que hace falta no es un padre, sino un contrato: una lista de métodos que quien lo firme se compromete a tener. Eso es una interfaz.

Declarar e implementar una interfaz

interface se traduce como “interfaz”: la superficie por la que dos cosas se comunican, igual que la interfaz de un programa es por donde lo usas sin ver su interior.

Ejemplo: el contratojava
public interface Exportable {
    String aTexto();
}

Eso es todo: la palabra interface, y adentro las firmas de los métodos terminadas en ;, sin cuerpo. Una clase se compromete a cumplirlo con implements (“implementa”):

Ejemplo: dos clases sin ningún parentesco firmando el mismo contratojava
public class Factura implements Exportable {
    private int numero;
    private double total;

    public Factura(int numero, double total) {
        this.numero = numero;
        this.total = total;
    }

    @Override
    public String aTexto() {
        return "FACTURA;" + numero + ";" + total;
    }
}

public class Cancion implements Exportable {
    private String titulo;
    private int segundos;

    public Cancion(String titulo, int segundos) {
        this.titulo = titulo;
        this.segundos = segundos;
    }

    @Override
    public String aTexto() {
        return "CANCION;" + titulo + ";" + segundos;
    }
}

Si una clase escribe implements Exportable y no define aTexto(), no compila: Factura is not abstract and does not override abstract method aTexto(). El contrato se hace cumplir en el compilador, no en la buena voluntad de quien escribe.

Una interfaz también es un tipo

Y aquí está la ganancia, la misma del polimorfismo pero sin necesitar un padre común:

Ejemplo: un método que sirve para cualquier cosa exportablejava
public static void guardarTodo(ArrayList<Exportable> items) {
    for (Exportable item : items) {
        System.out.println(item.aTexto());
    }
}
javajava
ArrayList<Exportable> items = new ArrayList<>();
items.add(new Factura(1001, 25990.5));
items.add(new Cancion("Bohemian Rhapsody", 355));
guardarTodo(items);
texttext
FACTURA;1001;25990.5
CANCION;Bohemian Rhapsody;355

guardarTodo no sabe nada de facturas ni de canciones: solo sabe que lo que reciba tiene un aTexto(). Cualquier clase que se escriba mañana funcionará con este método sin tocarle una línea.

Varias interfaces a la vez

Esta es la diferencia práctica más grande con la herencia: extends va una sola vez, implements acepta una lista separada por comas.

Ejemplo: una clase con dos capacidades independientesjava
public interface Exportable {
    String aTexto();
}

public interface Comparable2 {
    int compararCon(Object otro);
}

public class Factura implements Exportable, Comparable2 {
    // debe definir aTexto() Y compararCon()
}

Y las dos cosas se combinan: una clase puede heredar de una y firmar varias interfaces, en ese orden.

javajava
public class Alumno extends Persona implements Exportable {
    // hereda de Persona, además cumple el contrato Exportable
}

Interfaz o clase abstracta: cuál usar

Las dos definen métodos que las hijas deben implementar, y al principio se confunden. La tabla:

Clase abstractinterface
Palabra en la hijaextendsimplements
¿Cuántas se pueden tener?Una solaTodas las que quieras
¿Puede tener atributos con estado?Sí, normalesNo (solo constantes public static final)
¿Puede tener constructor?No
¿Métodos con cuerpo?Sí, los que quieraSí, pero marcados default o static
La relación que expresaes un” (un Circulo es una Figura)es capaz de” (una Factura es exportable)

La pregunta que decide, casi siempre:

  • ¿Las clases comparten estado y son variantes de una misma cosa? → clase abstracta. Circulo y Rectangulo son los dos figuras y comparten un nombre.
  • ¿Las clases no tienen nada en común salvo que saben hacer algo? → interfaz. Factura y Cancion solo comparten que saben exportarse.
Nota

La convención de nombres lo refleja: las clases abstractas suelen ser sustantivos (Figura, Vehiculo, Persona) y las interfaces suelen ser adjetivos terminados en -able o -ible (Exportable, Comparable, Runnable, Serializable). Si el nombre que se te ocurre termina en -able, casi seguro querías una interfaz.

Métodos default: cuerpo dentro de una interfaz

Desde Java 8, una interfaz puede traer implementaciones ya escritas marcadas con default:

Ejemplo: un método que todas heredan sin escribirlojava
public interface Exportable {
    String aTexto();

    default String aTextoConEtiqueta(String etiqueta) {
        return "[" + etiqueta + "] " + aTexto();
    }
}

Toda clase que implemente Exportable recibe aTextoConEtiqueta gratis, y puede sobrescribirlo si le conviene. Se agregó al lenguaje para poder ampliar interfaces ya publicadas sin romper el código existente: sin default, añadir un método a una interfaz haría dejar de compilar a todas las clases del mundo que la implementan.

Advertencia

Que existan métodos default no convierte a las interfaces en clases abstractas. Siguen sin poder tener atributos con estado ni constructor, y ahí está la línea que las separa. Úsalos para completar el contrato, no para meter lógica de negocio.

Interfaces que ya usas sin saberlo

Buena parte de la biblioteca estándar está construida sobre interfaces. Dos que aparecen enseguida:

Comparable<T> — la que hace que ordenar funcione. Su único método es compareTo, que devuelve un número negativo, cero o positivo según el objeto vaya antes, igual o después que el otro:

Ejemplo: hacer que una clase propia se pueda ordenarjava
public class Alumno implements Comparable<Alumno> {
    private String nombre;
    private double nota;

    public Alumno(String nombre, double nota) {
        this.nombre = nombre;
        this.nota = nota;
    }

    @Override
    public int compareTo(Alumno otro) {
        return Double.compare(this.nota, otro.nota);
    }

    public String getNombre() {
        return nombre;
    }
}
javajava
ArrayList<Alumno> curso = new ArrayList<>();
curso.add(new Alumno("Ana", 8.5));
curso.add(new Alumno("Beto", 6.0));
curso.add(new Alumno("Caro", 9.5));

Collections.sort(curso);   // usa compareTo automáticamente

for (Alumno a : curso) {
    System.out.println(a.getNombre());
}
texttext
Beto
Ana
Caro

Collections.sort fue escrito años antes que tu clase Alumno y no sabe nada de notas. Solo sabe que lo que le pasen implementa Comparable, y con eso le alcanza. Esa es la utilidad de las interfaces en una frase.

List<E> — el motivo de una convención que verás mucho:

javajava
List<String> nombres = new ArrayList<>();

List es la interfaz y ArrayList una de sus implementaciones. Declarar la variable con el tipo de la interfaz deja el resto del código independiente de cuál lista se está usando: cambiar a LinkedList es tocar una línea.

Pon a prueba

🎯 ¿Interfaz o clase abstracta?

Tienes tres clases que ya existen y no se parecen en nada — Usuario, Pedido y Mensaje — y necesitas que las tres sepan guardarse en disco con un método guardar(), para poder escribir un único método guardarTodo(...) que reciba una lista mezclada. ¿Qué conviene declarar?

  • Chocaría con el diseño: Usuario y Pedido probablemente ya heredan de otra cosa, y extends solo se puede usar una vez. Además no comparten ningún estado, solo una capacidad.

  • Correcto. Usuario, Pedido y Mensaje no son variantes de una misma cosa; lo único que comparten es saber guardarse. Como interfaz pueden firmarla sin renunciar a su propio extends, y el nombre terminado en -able ya lo estaba anticipando.

  • Tiene los problemas de la clase abstracta y uno más: new Guardable() sería instanciable, aunque un "guardable" suelto no signifique nada.

  • Funcionaría para cada clase por su lado, pero sin un tipo común no podrías escribir un método que reciba "cualquier cosa guardable" ni meterlas todas en la misma lista. Justamente lo que un contrato te da.

✏️ Completa la interfaz y su implementación

Reproducible define la capacidad de reproducirse. Podcast la cumple. Escribe las palabras clave que faltan.

public  Reproducible {
  void reproducir()
}

public class Podcast  Reproducible {
  private String titulo;

  
  public void reproducir() {
      System.out.println("Reproduciendo " + titulo);
  }
}

💡 ¿Por qué no compila?

Este código da dos errores de compilación distintos. ¿Cuáles son?

javajava
public interface Dibujable {
    int grosor = 2;
    void dibujar();
}

public class Linea implements Dibujable {
    @Override
    public void dibujar() {
        grosor = 5;
        System.out.println("Línea de grosor " + grosor);
    }
}

// en main:
Dibujable d = new Dibujable();

Error 1 — grosor = 5;. Un atributo declarado dentro de una interfaz es implícitamente public static final, aunque no lo escribas: es una constante, no un atributo de instancia. Asignarle un valor nuevo da cannot assign a value to final variable grosor. Esta es la razón técnica detrás de “las interfaces no tienen estado” de la tabla comparativa.

Error 2 — new Dibujable(). Una interfaz no se puede instanciar, por el mismo motivo que una clase abstracta: no tiene constructor ni implementación que ejecutar. Dibujable sirve perfectamente como tipo de la variable (Dibujable d = new Linea(); es correcto), pero lo que va del lado derecho del = tiene que ser una clase concreta que la implemente.

🧩 Ordena el código

Ordena las líneas para declarar una interfaz Pagable y una clase que la implementa.

  1. }
  2. @Override
  3. return tarifa * 1.19;
  4. public interface Pagable {
  5. }
  6. }
  7. private double tarifa;
  8. public class Servicio implements Pagable {
  9. double montoAPagar();
  10. public double montoAPagar() {

Siguiente paso

Con clases, herencia, polimorfismo e interfaces ya tienes las cuatro piezas grandes de la orientación a objetos. Falta cerrar un detalle que apareció desde el primer public static void main: qué significa exactamente que algo sea static, y qué garantiza final — ver Object, toString y equals primero, y después static y final.