Interfaces en Java — ejercicios con figuras, una biblioteca y sensores (y el cast que funcionó hasta que llegó una camiseta)

Tenía un catálogo con libros y escribí, muy contento, Libro l = (Libro) v; dentro de un bucle sobre un array de Vendible. Funcionó el lunes, el martes y el miércoles. El jueves añadí una camiseta al catálogo y el programa se cayó con un ClassCastException. El cast no había cambiado: había cambiado el contenido del array. El compilador me dejó hacer el cast porque podía ser correcto; el programa falló porque, esta vez, no lo era.

Aprendí a la mala que, con interfaces, el tipo de la variable no te dice qué objeto hay dentro. Hoy vamos a practicarlo con ejercicios: tres programas de dificultad creciente, tres fragmentos para depurar y un chuletario para llevarte.

Estos ejercicios usan la teoría de interfaces y repasan lo de la práctica de interfaces. Sin ellas puedes hacerlos, pero te costará más.

Cómo usar este artículo

  1. Lee el enunciado de un nivel y no mires la solución todavía.
  2. Escribe tu versión, compílala y compara tu salida con la esperada.
  3. Si te atascas, mira solo la 🔵 pista y vuelve a intentarlo.
  4. Cuando termines, compara con la solución y fíjate en por qué está hecha así, no solo en que dé la misma salida.

Ningún ejercicio usa Scanner. Los datos van fijos en el código para que tu salida sea idéntica a la esperada y puedas comprobarla de un vistazo.

Nivel 1 · Básico: figuras con Medible

Enunciado. Crea una interfaz Medible con:

  • double area()
  • String nombre()
  • un método default String describir() que devuelva el nombre y el área con dos decimales, por ejemplo Círculo con área 12,57.

Crea tres clases que la implementen: Rectangulo (base y altura), Circulo (radio) y Cuadrado (lado). En una clase Figuras con main, guarda en un array de tipo Medible un rectángulo de 3 × 4, un círculo de radio 2 y un cuadrado de lado 3,5. Escribe dos métodos estáticos:

  • areaTotal(Medible[]): suma las áreas.
  • mayor(Medible[]): devuelve la figura de mayor área.

El main imprime la descripción de cada figura, el área total y la mayor.

Salida esperada:

Rectángulo con área 12,00
Círculo con área 12,57
Cuadrado con área 12,25
Área total: 36,82
Mayor: Círculo con área 12,57

describir() no necesita saber qué figura es: le basta con nombre() y area(), que ya son parte del contrato. Para mayor, parte de la primera figura y compara con las demás.

Nivel 2 · Intermedio: una biblioteca

Enunciado. Una biblioteca presta libros, alquila salas y presta portátiles que también se pueden reservar por días. Modélalo con interfaces:

  • Recurso: tiene String nombre().
  • Prestable extiende Recurso: boolean prestar(String socio), void devolver(), boolean disponible() y un default String estado() que devuelva nombre: disponible o nombre: prestado.
  • Reservable extiende Recurso: boolean reservar(String socio, String dia) (días de Lunes a Viernes, devuelve false si el día ya está ocupado o no existe), String reservadoPor(String dia) (null si está libre) y un default boolean libre(String dia).
  • Libro es Prestable, Sala es Reservable y Portatil es las dos cosas.

En Biblioteca.main, crea un array Recurso[] con el libro «Java para FP2», la «Sala 3» y el «Portátil 07», y ejecuta este guion:

  1. Ana pide prestado todo lo que sea prestable.
  2. Luis intenta pedir el libro.
  3. Luis reserva la Sala 3 el Lunes.
  4. Marta reserva la Sala 3 el Lunes.
  5. Marta reserva el Portátil 07 el Martes.
  6. Se imprime el estado de cada recurso (el préstamo de lo prestable y las reservas del Lunes y el Martes de lo reservable).

Usa estos mensajes: Ana pide Java para FP2: OK (o no disponible), Luis reserva Sala 3 el Lunes: OK (u ocupada; con el portátil, ocupado), después una línea --- Estado --- y, para lo reservable, Sala 3: Lunes de Luis, Martes libre.

Salida esperada:

Ana pide Java para FP2: OK
Ana pide Portátil 07: OK
Luis pide Java para FP2: no disponible
Luis reserva Sala 3 el Lunes: OK
Marta reserva Sala 3 el Lunes: ocupada
Marta reserva Portátil 07 el Martes: OK
--- Estado ---
Java para FP2: prestado
Sala 3: Lunes de Luis, Martes libre
Portátil 07: prestado
Portátil 07: Lunes libre, Martes de Marta

El array es de tipo Recurso, así que para prestar o reservar tienes que preguntar primero con instanceof Prestable / instanceof Reservable. El portátil responde que sí a las dos preguntas, por eso sale dos veces en el estado.

Nivel 3 · Desafío: una estación de sensores

Enunciado. Una estación meteorológica tiene sensores de varios tipos. Define:

  • Sensor: String id(), String unidad(), double leer(), un método estático formato(double valor, String unidad) que devuelva el valor con un decimal y la unidad (27,5 °C) y un default String resumen() que devuelva id (unidad).
  • Calibrable extiende Sensor: void calibrar(double offset) y un default resumen() que añada , calibrable al de Sensor.
  • Alarmable extiende Sensor: double umbral(), un default boolean enAlarma() (leer() > umbral()) y un default resumen() que añada , alarma > umbral al de Sensor.
  • Termometro implementa **Calibrable y Alarmable**, así que tiene dos resumen() en conflicto. Resuélvelo para que muestre T1 (°C), alarma > 30,0 °C, calibrable.
  • Humedad solo es Sensor. Presion solo es Alarmable.

En Estacion, crea el array con: termómetros T1 (27,5 °C, umbral 30) y T2 (32,1 °C, umbral 30), un sensor de humedad H1 (61,0 %) y uno de presión P1 (1013,2 hPa, umbral 1020). Escribe un método informe(Sensor[], String titulo) que imprima una línea === titulo ===, después cada resumen seguido de -> y su lectura, añada ⚠ ALARMA cuando proceda y termine con Alarmas: n. Imprime el informe con el título «Informe inicial», calibra todos los calibrables en +3,0 y vuelve a imprimirlo con el título «Tras calibrar».

Salida esperada:

=== Informe inicial ===
T1 (°C), alarma > 30,0 °C, calibrable -> 27,5 °C
T2 (°C), alarma > 30,0 °C, calibrable -> 32,1 °C  ⚠ ALARMA
H1 (%) -> 61,0 %
P1 (hPa), alarma > 1020,0 hPa -> 1013,2 hPa
Alarmas: 1
=== Tras calibrar ===
T1 (°C), alarma > 30,0 °C, calibrable -> 30,5 °C  ⚠ ALARMA
T2 (°C), alarma > 30,0 °C, calibrable -> 35,1 °C  ⚠ ALARMA
H1 (%) -> 61,0 %
P1 (hPa), alarma > 1020,0 hPa -> 1013,2 hPa
Alarmas: 2

En Termometro, resumen() debe llamar a una de las dos versiones con Interfaz.super.resumen() y completar lo que falte. Fíjate en que el resumen que sale en el informe de un termómetro tiene los dos sufijos (alarma y calibrable) pero solo una vez el prefijo T1 (°C).

¿Qué falla aquí?

Tres fragmentos con un fallo cada uno. Antes de leer la explicación, haz dos cosas: predice si compila y, si compila, qué imprime (o qué pasa al ejecutarlo).

Fragmento 1

interface Vendible {
    String nombre();
}

class Libro implements Vendible {
    public String nombre() { return "Libro"; }
    public String autor() { return "Sergio"; }
}

class Camiseta implements Vendible {
    public String nombre() { return "Camiseta"; }
}

public class F1 {
    public static void main(String[] args) {
        Vendible[] catalogo = { new Libro(), new Camiseta() };

        for (Vendible v : catalogo) {
            Libro l = (Libro) v;
            System.out.println(l.nombre() + " de " + l.autor());
        }
    }
}

Piénsalo antes de seguir: ¿compila? ¿qué imprime?

Explicación. Compila sin problemas: el compilador acepta un cast de Vendible a Libro porque algún Vendible podría ser un Libro. Pero en la segunda vuelta del bucle v es una Camiseta:

Libro de Sergio
Exception in thread "main" java.lang.ClassCastException: class Camiseta cannot be cast to class Libro (Camiseta and Libro are in unnamed module of loader 'app')
	at F1.main(F1.java:19)

«ClassCastException … cannot be cast to class …». Un cast hacia abajo es una promesa al compilador que se comprueba al ejecutar. Si el objeto no es de esa clase, el programa se cae. La primera línea («Libro de Sergio») sí salió, porque el primer objeto sí era un Libro.

Arreglo. Pregunta antes de convertir:

for (Vendible v : catalogo) {
    if (v instanceof Libro l) {
        System.out.println(l.nombre() + " de " + l.autor());
    } else {
        System.out.println(v.nombre() + " (sin autor)");
    }
}
Libro de Sergio
Camiseta (sin autor)

Con instanceof Libro l la comprobación y la conversión van en un solo paso, y l solo existe dentro del if. No hay forma de olvidarse de la comprobación.

Fragmento 2

interface Amable {
    default String saludo() { return "Hola"; }
}

interface Formal {
    default String saludo() { return "Buenos días"; }
}

class Recepcionista implements Amable, Formal {
}

public class F2 {
    public static void main(String[] args) {
        System.out.println(new Recepcionista().saludo());
    }
}

¿Compila? ¿qué imprime?

Explicación. No compila. Recepcionista hereda dos métodos saludo() por defecto y Java no sabe cuál usar:

F2.java:9: error: types Amable and Formal are incompatible;
class Recepcionista implements Amable, Formal {
^
  class Recepcionista inherits unrelated defaults for saludo() from types Amable and Formal
1 error

«inherits unrelated defaults for saludo() from types …». Dos interfaces, un mismo default, y ninguna decisión: el diamante. Java prefiere obligarte a decidir antes que elegir mal por ti.

Arreglo. La clase sobrescribe el método y decide qué hacer, llamando a cada versión con Interfaz.super:

class Recepcionista implements Amable, Formal {
    @Override
    public String saludo() {                         // decidimos nosotros
        return Formal.super.saludo() + ", " + Amable.super.saludo().toLowerCase();
    }
}
Buenos días, hola

Fragmento 3

interface Config {
    int REINTENTOS = 3;
}

class Cliente implements Config {
    void conectar() {
        while (REINTENTOS > 0) {
            System.out.println("Intentando… quedan " + REINTENTOS);
            REINTENTOS--;
        }
    }
}

public class F3 {
    public static void main(String[] args) {
        new Cliente().conectar();
    }
}

¿Compila? ¿qué imprime?

Explicación. No compila. REINTENTOS está declarada en una interfaz, así que es public static final aunque no lo escribas. Y una constante no se puede modificar:

F3.java:9: error: cannot assign a value to static final variable REINTENTOS
            REINTENTOS--;
            ^
1 error

«cannot assign a value to static final variable». Los campos de una interfaz son constantes, por muchas clases que los hereden. Si necesitas un contador que cambie, no es una constante.

Arreglo. Copia el valor a una variable local y modifica la copia:

class Cliente implements Config {
    void conectar() {
        int restantes = REINTENTOS;                  // copia local: esa sí se puede cambiar
        while (restantes > 0) {
            System.out.println("Intentando… quedan " + restantes);
            restantes--;
        }
    }
}
Intentando… quedan 3
Intentando… quedan 2
Intentando… quedan 1

Soluciones

Mira esta sección solo cuando hayas intentado el ejercicio. Si tu código es distinto pero da la misma salida y cumple los contratos, también es una solución válida.

Solución del Nivel 1

Medible.java

public interface Medible {
    double area();
    String nombre();

    default String describir() {          // se apoya en nombre() y area(): vale para todas las figuras
        return nombre() + " con área " + String.format("%.2f", area());
    }
}

Rectangulo.java

public class Rectangulo implements Medible {
    private final double base, altura;

    public Rectangulo(double base, double altura) {
        this.base = base;
        this.altura = altura;
    }

    @Override public double area() { return base * altura; }
    @Override public String nombre() { return "Rectángulo"; }
}

Circulo.java

public class Circulo implements Medible {
    private final double radio;

    public Circulo(double radio) { this.radio = radio; }

    @Override public double area() { return Math.PI * radio * radio; }
    @Override public String nombre() { return "Círculo"; }
}

Cuadrado.java

public class Cuadrado implements Medible {
    private final double lado;

    public Cuadrado(double lado) { this.lado = lado; }

    @Override public double area() { return lado * lado; }
    @Override public String nombre() { return "Cuadrado"; }
}

Figuras.java

public class Figuras {

    static double areaTotal(Medible[] figuras) {
        double total = 0;
        for (Medible m : figuras) {
            total += m.area();
        }
        return total;
    }

    static Medible mayor(Medible[] figuras) {
        Medible mayor = figuras[0];   // partimos de la primera y vamos comparando
        for (Medible m : figuras) {
            if (m.area() > mayor.area()) {
                mayor = m;
            }
        }
        return mayor;
    }

    public static void main(String[] args) {
        Medible[] figuras = { new Rectangulo(3, 4), new Circulo(2), new Cuadrado(3.5) };

        for (Medible m : figuras) {
            System.out.println(m.describir());
        }
        System.out.printf("Área total: %.2f%n", areaTotal(figuras));
        System.out.println("Mayor: " + mayor(figuras).describir());
    }
}

Por qué está hecho así:

  • describir() es default porque no depende de ninguna figura concreta: usa nombre() y area(), que son del contrato. Las tres figuras lo reciben gratis.
  • Los métodos areaTotal y mayor reciben Medible[]. Si mañana añades un Triangulo, no hay que tocarlos.
  • mayor parte de figuras[0]: es más seguro que partir de 0 o de un valor inventado, porque la figura inicial es una de las reales.

Solución del Nivel 2

Recurso.java, Prestable.java y Reservable.java

public interface Recurso {
    String nombre();
}
public interface Prestable extends Recurso {   // hereda nombre() de Recurso
    boolean prestar(String socio);
    void devolver();
    boolean disponible();

    default String estado() {
        return nombre() + ": " + (disponible() ? "disponible" : "prestado");
    }
}
public interface Reservable extends Recurso {
    boolean reservar(String socio, String dia);
    String reservadoPor(String dia);   // null si el día está libre

    default boolean libre(String dia) {   // se apoya en reservadoPor
        return reservadoPor(dia) == null;
    }
}

Libro.java

public class Libro implements Prestable {
    private final String titulo;
    private String prestadoA;

    public Libro(String titulo) { this.titulo = titulo; }

    @Override public String nombre() { return titulo; }
    @Override public boolean disponible() { return prestadoA == null; }

    @Override
    public boolean prestar(String socio) {
        if (!disponible()) return false;
        prestadoA = socio;
        return true;
    }

    @Override public void devolver() { prestadoA = null; }
}

Sala.java

public class Sala implements Reservable {
    private static final String[] DIAS = { "Lunes", "Martes", "Miércoles", "Jueves", "Viernes" };
    private final String nombre;
    private final String[] reservas = new String[DIAS.length];

    public Sala(String nombre) { this.nombre = nombre; }

    @Override public String nombre() { return nombre; }

    private int indice(String dia) {
        for (int i = 0; i < DIAS.length; i++) {
            if (DIAS[i].equals(dia)) return i;
        }
        return -1;
    }

    @Override
    public boolean reservar(String socio, String dia) {
        int i = indice(dia);
        if (i == -1 || reservas[i] != null) return false;
        reservas[i] = socio;
        return true;
    }

    @Override
    public String reservadoPor(String dia) {
        int i = indice(dia);
        return i == -1 ? null : reservas[i];
    }
}

Portatil.java

public class Portatil implements Prestable, Reservable {   // dos capacidades en una sola clase
    private static final String[] DIAS = { "Lunes", "Martes", "Miércoles", "Jueves", "Viernes" };
    private final String nombre;
    private String prestadoA;
    private final String[] reservas = new String[DIAS.length];

    public Portatil(String nombre) { this.nombre = nombre; }

    @Override public String nombre() { return nombre; }
    @Override public boolean disponible() { return prestadoA == null; }

    @Override
    public boolean prestar(String socio) {
        if (!disponible()) return false;
        prestadoA = socio;
        return true;
    }

    @Override public void devolver() { prestadoA = null; }

    private int indice(String dia) {
        for (int i = 0; i < DIAS.length; i++) {
            if (DIAS[i].equals(dia)) return i;
        }
        return -1;
    }

    @Override
    public boolean reservar(String socio, String dia) {
        int i = indice(dia);
        if (i == -1 || reservas[i] != null) return false;
        reservas[i] = socio;
        return true;
    }

    @Override
    public String reservadoPor(String dia) {
        int i = indice(dia);
        return i == -1 ? null : reservas[i];
    }
}

Biblioteca.java

public class Biblioteca {

    public static void main(String[] args) {
        Recurso[] recursos = {
            new Libro("Java para FP2"),
            new Sala("Sala 3"),
            new Portatil("Portátil 07")
        };

        // Ana pide prestado todo lo que se pueda prestar
        for (Recurso r : recursos) {
            if (r instanceof Prestable p) {   // solo si es prestable
                System.out.println("Ana pide " + r.nombre() + ": " + (p.prestar("Ana") ? "OK" : "no disponible"));
            }
        }
        // Luis lo intenta con el libro
        Prestable libro = (Prestable) recursos[0];
        System.out.println("Luis pide " + libro.nombre() + ": " + (libro.prestar("Luis") ? "OK" : "no disponible"));

        // Reservas
        Reservable sala = (Reservable) recursos[1];
        System.out.println("Luis reserva " + sala.nombre() + " el Lunes: " + (sala.reservar("Luis", "Lunes") ? "OK" : "ocupada"));
        System.out.println("Marta reserva " + sala.nombre() + " el Lunes: " + (sala.reservar("Marta", "Lunes") ? "OK" : "ocupada"));
        Reservable portatil = (Reservable) recursos[2];
        System.out.println("Marta reserva " + portatil.nombre() + " el Martes: " + (portatil.reservar("Marta", "Martes") ? "OK" : "ocupado"));

        // Estados
        System.out.println("--- Estado ---");
        for (Recurso r : recursos) {
            if (r instanceof Prestable p) {   // solo si es prestable
                System.out.println(p.estado());
            }
            if (r instanceof Reservable rv) {   // solo si es reservable
                System.out.println(r.nombre() + ": Lunes " + (rv.libre("Lunes") ? "libre" : "de " + rv.reservadoPor("Lunes"))
                        + ", Martes " + (rv.libre("Martes") ? "libre" : "de " + rv.reservadoPor("Martes")));
            }
        }
    }
}

Por qué está hecho así:

  • Prestable y Reservable extienden Recurso. Así el array puede ser de tipo Recurso (todo tiene nombre), y cualquier clase que implemente una de las dos queda obligada a tener nombre().
  • Portatil implementa las dos interfaces: por eso responde a los dos instanceof y aparece dos veces en el estado. No hay conflicto de default: estado() es solo de Prestable y libre() es solo de Reservable.
  • reservadoPor devuelve null para el día libre y libre() se apoya en él. Es un buen ejemplo de default que usa el contrato para construir algo nuevo.
  • Sala y Portatil repiten el código de las reservas. Es adrede, para centrarnos en las interfaces; si quisieras evitarlo, podrías sacar esa parte a una clase auxiliar y reutilizarla.

Solución del Nivel 3

Sensor.java, Calibrable.java y Alarmable.java

public interface Sensor {
    String id();
    String unidad();
    double leer();

    static String formato(double valor, String unidad) {   // se llama como Sensor.formato(...)
        return String.format("%.1f %s", valor, unidad);
    }

    default String resumen() {
        return id() + " (" + unidad() + ")";
    }
}
public interface Calibrable extends Sensor {
    void calibrar(double offset);

    @Override
    default String resumen() {
        return Sensor.super.resumen() + ", calibrable";
    }
}
public interface Alarmable extends Sensor {
    double umbral();

    default boolean enAlarma() {
        return leer() > umbral();
    }

    @Override
    default String resumen() {
        return Sensor.super.resumen() + ", alarma > " + Sensor.formato(umbral(), unidad());
    }
}

Termometro.java

public class Termometro implements Calibrable, Alarmable {
    private final String id;
    private double lectura;
    private final double umbral;

    public Termometro(String id, double lectura, double umbral) {
        this.id = id;
        this.lectura = lectura;
        this.umbral = umbral;
    }

    @Override public String id() { return id; }
    @Override public String unidad() { return "°C"; }
    @Override public double leer() { return lectura; }
    @Override public double umbral() { return umbral; }
    @Override public void calibrar(double offset) { lectura += offset; }

    // Dos interfaces traen su propio resumen(): hay que decidir cuál manda
    @Override
    public String resumen() {
        return Alarmable.super.resumen() + ", calibrable";
    }
}

Humedad.java y Presion.java

public class Humedad implements Sensor {
    private final String id;
    private final double lectura;

    public Humedad(String id, double lectura) {
        this.id = id;
        this.lectura = lectura;
    }

    @Override public String id() { return id; }
    @Override public String unidad() { return "%"; }
    @Override public double leer() { return lectura; }
}
public class Presion implements Alarmable {
    private final String id;
    private final double lectura;
    private final double umbral;

    public Presion(String id, double lectura, double umbral) {
        this.id = id;
        this.lectura = lectura;
        this.umbral = umbral;
    }

    @Override public String id() { return id; }
    @Override public String unidad() { return "hPa"; }
    @Override public double leer() { return lectura; }
    @Override public double umbral() { return umbral; }
}

Estacion.java

public class Estacion {

    static void informe(Sensor[] sensores, String titulo) {
        System.out.println("=== " + titulo + " ===");
        int alarmas = 0;
        for (Sensor s : sensores) {
            String linea = s.resumen() + " -> " + Sensor.formato(s.leer(), s.unidad());
            if (s instanceof Alarmable a && a.enAlarma()) {   // ¿tiene la capacidad? ¿y está activada?
                linea += "  ⚠ ALARMA";
                alarmas++;
            }
            System.out.println(linea);
        }
        System.out.println("Alarmas: " + alarmas);
    }

    public static void main(String[] args) {
        Sensor[] sensores = {
            new Termometro("T1", 27.5, 30),
            new Termometro("T2", 32.1, 30),
            new Humedad("H1", 61.0),
            new Presion("P1", 1013.2, 1020)
        };

        informe(sensores, "Informe inicial");

        for (Sensor s : sensores) {
            if (s instanceof Calibrable c) {   // solo los calibrables
                c.calibrar(3.0);
            }
        }

        informe(sensores, "Tras calibrar");
    }
}

Por qué está hecho así:

  • Sensor.super.resumen() dentro de Calibrable y de Alarmable permite que cada capacidad añada su parte sin repetir lo común.
  • Termometro hereda dos resumen() y tiene que decidir. Aquí llamamos a Alarmable.super.resumen() (que ya incluye T1 (°C) y la alarma) y añadimos , calibrable a mano. No podemos llamar a las dos versiones, porque el prefijo T1 (°C) saldría duplicado.
  • Sensor.formato(...) es un método static de la interfaz: se llama con el nombre de la interfaz, y lo usan tanto Alarmable como Estacion.
  • En informe, s instanceof Alarmable a && a.enAlarma() pregunta por la capacidad y por su estado en una sola condición. La humedad (que no es Alarmable) nunca llega a la segunda parte.
  • Tras calibrar, T1 pasa de 27,5 a 30,5 y supera su umbral: el segundo informe tiene dos alarmas.

Míralo paso a paso en Python Tutor

Python Tutor ejecuta el código línea a línea y dibuja variables, objetos y referencias. Elige Java. Aquí tienes el fragmento 1 ya arreglado, con el instanceof y el cast clásicos para que se vea bien cada paso:

public class Tutor {
    interface Vendible { String nombre(); }

    static class Libro implements Vendible {
        public String nombre() { return "Libro"; }
        public String autor() { return "Sergio"; }
    }

    static class Camiseta implements Vendible {
        public String nombre() { return "Camiseta"; }
    }

    public static void main(String[] args) {
        Vendible[] catalogo = { new Libro(), new Camiseta() };

        for (int i = 0; i < catalogo.length; i++) {
            Vendible v = catalogo[i];
            if (v instanceof Libro) {
                Libro l = (Libro) v;
                System.out.println(l.nombre() + " de " + l.autor());
            } else {
                System.out.println(v.nombre() + " (sin autor)");
            }
        }
    }
}

Salida esperada:

Libro de Sergio
Camiseta (sin autor)

Qué observar paso a paso:

  1. En el array catalogo hay dos objetos distintos: un Libro y una Camiseta. Los dos están guardados como Vendible, pero cada uno conserva su clase.
  2. En la primera vuelta, v instanceof Libro es verdadero. Entonces Libro l = (Libro) v crea una variable l que apunta al mismo objeto que v: no se copia nada, solo hay una flecha más.
  3. En la segunda vuelta, v apunta a la Camiseta. La condición da falso y se salta el bloque: el cast ni se ejecuta.
  4. Ahora modifica el programa: borra el if y deja solo Libro l = (Libro) v;. Al llegar a la segunda vuelta debería detenerse con la excepción ClassCastException: es el fallo del fragmento 1 en directo.

Chuletario: interfaces

Lo esencial en 30 segundos

  • Una interfaz es un contrato: dice qué se puede hacer, no cómo.
  • Se cumple con implements y hay que sobrescribir todo lo abstracto, con public.
  • Una clase tiene un padre (extends) y muchas interfaces (implements A, B).
  • Una variable de tipo interfaz guarda cualquier objeto que la cumpla, pero solo ve lo que el contrato declara.
  • Para usar lo propio de una clase: instanceof primero, conversión después.
  • default da un cuerpo opcional; static es de la interfaz; los campos son constantes.

Sintaxis

Quiero…CódigoResultado
Declarar un contratointerface Medible { double area(); }Método public abstract
Cumplirloclass Circulo implements Medible { public double area() {…} }Obligatorio public
Varias interfacesclass Portatil implements Prestable, ReservableCumple los dos contratos
Extender una interfazinterface Prestable extends RecursoHereda sus métodos
Variable de tipo interfazMedible m = new Circulo(2);Sin cast, hacia arriba
Array de tipo interfazMedible[] figuras = {…};Mezcla clases distintas
Preguntar la capacidadif (r instanceof Prestable p) { p.prestar(…); }Comprueba y convierte
Cast clásicoLibro l = (Libro) v;ClassCastException si no lo es
Método con cuerpodefault String describir() {…}Opcional de sobrescribir
Elegir en un conflictoAlarmable.super.resumen()Llama a esa versión
Utilidad de la interfazstatic String formato(…) {…}Sensor.formato(…)
Constanteint MAX = 3;public static final

Plantillas de los ejercicios

**Un default que se apoya en el contrato** (Nivel 1):

default String describir() {
    return nombre() + " con área " + String.format("%.2f", area());
}

Preguntar por la capacidad antes de usarla (Nivel 2):

for (Recurso r : recursos) {
    if (r instanceof Prestable p) {
        p.prestar("Ana");
    }
}

**Resolver un conflicto de default** (Nivel 3):

@Override
public String resumen() {
    return Alarmable.super.resumen() + ", calibrable";
}

Errores que más se repiten

SíntomaCausa probable
«attempting to assign weaker access privileges; was public»Falta public al sobrescribir un método de la interfaz
«is not abstract and does not override abstract method…»Falta implementar algún método del contrato
«Pagable is abstract; cannot be instantiated»Has escrito new con una interfaz
«cannot find symbol» con una variable de tipo interfazUsas un método propio de la clase sin instanceof + conversión
«incompatible types: X cannot be converted to Y»Falta el cast hacia abajo
ClassCastExceptionCast sin comprobar el tipo real con instanceof
«inherits unrelated defaults for…»Dos default iguales: sobrescribe y usa Interfaz.super
«cannot assign a value to static final variable»Intentas cambiar una constante de la interfaz

Antes de entregar, pregúntate

  1. ¿He escrito **public** en todos los métodos que implementan la interfaz?
  2. ¿Pregunto con **instanceof** antes de cualquier cast hacia abajo?
  3. ¿Mi código depende de la interfaz (Medible, Prestable) y no de clases concretas?
  4. Si dos interfaces traen el mismo default, ¿he decidido yo cuál manda?

Si quieres repasar el tema desde la base, vuelve a la teoría de interfaces.

Sigue aprendiendoTodos los artículos de Java

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *