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.
Tabla de Contenidos
Cómo usar este artículo
- Lee el enunciado de un nivel y no mires la solución todavía.
- Escribe tu versión, compílala y compara tu salida con la esperada.
- Si te atascas, mira solo la 🔵 pista y vuelve a intentarlo.
- 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 ejemploCí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: tieneString nombre().PrestableextiendeRecurso:boolean prestar(String socio),void devolver(),boolean disponible()y undefault String estado()que devuelvanombre: disponibleonombre: prestado.ReservableextiendeRecurso:boolean reservar(String socio, String dia)(días de Lunes a Viernes, devuelvefalsesi el día ya está ocupado o no existe),String reservadoPor(String dia)(nullsi está libre) y undefault boolean libre(String dia).LibroesPrestable,SalaesReservableyPortatiles 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:
- Ana pide prestado todo lo que sea prestable.
- Luis intenta pedir el libro.
- Luis reserva la Sala 3 el Lunes.
- Marta reserva la Sala 3 el Lunes.
- Marta reserva el Portátil 07 el Martes.
- 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áticoformato(double valor, String unidad)que devuelva el valor con un decimal y la unidad (27,5 °C) y undefault String resumen()que devuelvaid (unidad).CalibrableextiendeSensor:void calibrar(double offset)y undefault resumen()que añada, calibrableal deSensor.AlarmableextiendeSensor:double umbral(), undefault boolean enAlarma()(leer() > umbral()) y undefault resumen()que añada, alarma > umbralal deSensor.Termometroimplementa **CalibrableyAlarmable**, así que tiene dosresumen()en conflicto. Resuélvelo para que muestreT1 (°C), alarma > 30,0 °C, calibrable.Humedadsolo esSensor.Presionsolo esAlarmable.
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()esdefaultporque no depende de ninguna figura concreta: usanombre()yarea(), que son del contrato. Las tres figuras lo reciben gratis.- Los métodos
areaTotalymayorrecibenMedible[]. Si mañana añades unTriangulo, no hay que tocarlos. mayorparte defiguras[0]: es más seguro que partir de0o 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í:
PrestableyReservableextiendenRecurso. Así el array puede ser de tipoRecurso(todo tiene nombre), y cualquier clase que implemente una de las dos queda obligada a tenernombre().Portatilimplementa las dos interfaces: por eso responde a los dosinstanceofy aparece dos veces en el estado. No hay conflicto dedefault:estado()es solo dePrestableylibre()es solo deReservable.reservadoPordevuelvenullpara el día libre ylibre()se apoya en él. Es un buen ejemplo dedefaultque usa el contrato para construir algo nuevo.SalayPortatilrepiten 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 deCalibrabley deAlarmablepermite que cada capacidad añada su parte sin repetir lo común.Termometrohereda dosresumen()y tiene que decidir. Aquí llamamos aAlarmable.super.resumen()(que ya incluyeT1 (°C)y la alarma) y añadimos, calibrablea mano. No podemos llamar a las dos versiones, porque el prefijoT1 (°C)saldría duplicado.Sensor.formato(...)es un métodostaticde la interfaz: se llama con el nombre de la interfaz, y lo usan tantoAlarmablecomoEstacion.- 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 esAlarmable) 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:
- En el array
catalogohay dos objetos distintos: unLibroy unaCamiseta. Los dos están guardados comoVendible, pero cada uno conserva su clase. - En la primera vuelta,
v instanceof Libroes verdadero. EntoncesLibro l = (Libro) vcrea una variablelque apunta al mismo objeto quev: no se copia nada, solo hay una flecha más. - En la segunda vuelta,
vapunta a laCamiseta. La condición da falso y se salta el bloque: el cast ni se ejecuta. - Ahora modifica el programa: borra el
ify deja soloLibro l = (Libro) v;. Al llegar a la segunda vuelta debería detenerse con la excepciónClassCastException: 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
implementsy hay que sobrescribir todo lo abstracto, conpublic. - 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:
instanceofprimero, conversión después. defaultda un cuerpo opcional;statices de la interfaz; los campos son constantes.
Sintaxis
| Quiero… | Código | Resultado |
|---|---|---|
| Declarar un contrato | interface Medible { double area(); } | Método public abstract |
| Cumplirlo | class Circulo implements Medible { public double area() {…} } | Obligatorio public |
| Varias interfaces | class Portatil implements Prestable, Reservable | Cumple los dos contratos |
| Extender una interfaz | interface Prestable extends Recurso | Hereda sus métodos |
| Variable de tipo interfaz | Medible m = new Circulo(2); | Sin cast, hacia arriba |
| Array de tipo interfaz | Medible[] figuras = {…}; | Mezcla clases distintas |
| Preguntar la capacidad | if (r instanceof Prestable p) { p.prestar(…); } | Comprueba y convierte |
| Cast clásico | Libro l = (Libro) v; | ClassCastException si no lo es |
| Método con cuerpo | default String describir() {…} | Opcional de sobrescribir |
| Elegir en un conflicto | Alarmable.super.resumen() | Llama a esa versión |
| Utilidad de la interfaz | static String formato(…) {…} | Sensor.formato(…) |
| Constante | int 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íntoma | Causa 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 interfaz | Usas un método propio de la clase sin instanceof + conversión |
| «incompatible types: X cannot be converted to Y» | Falta el cast hacia abajo |
ClassCastException | Cast 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
- ¿He escrito **
public** en todos los métodos que implementan la interfaz? - ¿Pregunto con **
instanceof** antes de cualquier cast hacia abajo? - ¿Mi código depende de la interfaz (
Medible,Prestable) y no de clases concretas? - 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 →
