Interfaces en Java — práctica con una pasarela de pagos, un carrito y un mando de dispositivos (y la clase abstracta que no pudo con dos padres)
Cuando me pidieron modelar un mando a distancia para una tele, un altavoz y una linterna, pensé: «fácil, clases abstractas». Creé Aparato (que se enciende y se apaga), después ConSonido (que reproduce música) y ConRed (que se conecta al WiFi). Hasta ahí, todo bien. Entonces llegó la tele, que enciende, suena y se conecta, y escribí sin pensar class Television extends Aparato, ConSonido, ConRed. Java me contestó con un seco '{' expected: una clase solo puede tener un padre.
Intenté apañarlo encadenando (ConRed extends ConSonido extends Aparato), pero entonces la linterna, que solo se enciende, acabó heredando un reproducir() que no quería ni sabía hacer. Ahí entendí para qué existen las interfaces: para describir capacidades sueltas que cada clase elige combinar. Esa es la columna vertebral de los tres programas de hoy.
Si ya has leído la teoría de interfaces, este artículo es para aplicarla: una pasarela de pagos que prueba medios de pago en orden, una tienda cuyo carrito acepta cualquier producto y cualquier medio de pago, y un mando que controla dispositivos con capacidades distintas. Son de dificultad media y trabajan con situaciones que se parecen a las de verdad. Cada uno trae el código comentado, la salida y las dudas que suelen surgir.
Tabla de Contenidos
Antes de empezar
Conviene tener claro:
- Que una clase implementa una interfaz con
implementsy debe sobrescribir todos sus métodos abstractos, siempre conpublic. - Que una variable de tipo interfaz (
Pagable p) puede guardar cualquier objeto cuya clase la implemente, y solo «ve» los métodos de la interfaz. - Que un método
defaulttrae un cuerpo que las clases pueden usar o sobrescribir, y que si dos interfaces traen el mismodefault, la clase tiene que decidir conInterfaz.super.metodo(). - Que
instanceofsirve para preguntar si un objeto cumple una capacidad antes de usarla.
Si alguno te suena dudoso, repasa la teoría antes de seguir.
Puedes meter todos los ficheros del artículo en una misma carpeta y compilarlos juntos. Los programas 1 y 2 comparten Pagable, Tarjeta y Bizum: el programa 2 reutiliza esas tres clases tal cual. El programa 3 va aparte.
Programa 1: pasarela de pagos
Una tienda online acepta varios medios de pago: Bizum, tarjeta y efectivo en tienda. Cuando llega una compra, la pasarela los prueba en orden y cobra con el primero que pueda. La tarjeta cobra una comisión del 1 %; los otros dos medios, no.
La idea
Todos los medios tienen que poder hacer dos cosas: intentar pagar un importe y decir cómo se llaman. Eso es el contrato, la interfaz Pagable. La pasarela solo conoce ese contrato, así que mañana podrás añadir un nuevo medio sin tocarla.
La comisión es un caso aparte: casi todos los medios no la cobran, pero la tarjeta sí. Si fuera abstracta, Bizum y Efectivo tendrían que escribir return 0 cada uno. Por eso es un método default que devuelve 0 y que la tarjeta sobrescribe.
Antes de programar, piensa en qué necesita saber Pasarela de cada medio: si puede pagar, cómo se llama y cuánto cobra de comisión. Ni una cosa más.
El código
Pagable.java
public interface Pagable {
boolean pagar(double importe);
String nombre();
// Por defecto no hay comisión; quien la cobre, la sobrescribe
default double comision(double importe) {
return 0;
}
}
Tarjeta.java
public class Tarjeta implements Pagable {
private double limite;
public Tarjeta(double limite) {
this.limite = limite;
}
@Override
public double comision(double importe) {
return importe * 0.01; // 1 % de comisión
}
@Override
public boolean pagar(double importe) {
double total = importe + comision(importe);
if (total > limite) {
return false;
}
limite -= total;
return true;
}
@Override
public String nombre() {
return "Tarjeta";
}
}
Bizum.java
public class Bizum implements Pagable {
private double saldo;
public Bizum(double saldo) {
this.saldo = saldo;
}
@Override
public boolean pagar(double importe) {
if (importe > saldo) {
return false;
}
saldo -= importe;
return true;
}
@Override
public String nombre() {
return "Bizum";
}
}
Efectivo.java
public class Efectivo implements Pagable {
private double cartera;
public Efectivo(double cartera) {
this.cartera = cartera;
}
@Override
public boolean pagar(double importe) {
if (importe > cartera) {
return false;
}
cartera -= importe;
return true;
}
@Override
public String nombre() {
return "Efectivo";
}
}
Pasarela.java
public class Pasarela {
// Prueba los medios EN ORDEN y devuelve el primero que consiga cobrar (o null)
static Pagable cobrar(Pagable[] medios, double importe) {
for (Pagable medio : medios) {
double total = importe + medio.comision(importe);
if (medio.pagar(importe)) {
System.out.printf(" %s: cobrado %.2f €%n", medio.nombre(), total);
return medio;
}
System.out.printf(" %s: rechazado%n", medio.nombre());
}
return null;
}
public static void main(String[] args) {
Pagable[] medios = { new Bizum(60), new Tarjeta(150), new Efectivo(25) };
double[] compras = { 45, 120, 300 };
for (int i = 0; i < compras.length; i++) {
System.out.printf("Compra %d: %.2f €%n", i + 1, compras[i]);
Pagable usado = cobrar(medios, compras[i]);
if (usado == null) {
System.out.println(" No se ha podido cobrar");
}
}
}
}
Salida
Compra 1: 45,00 € Bizum: cobrado 45,00 € Compra 2: 120,00 € Bizum: rechazado Tarjeta: cobrado 121,20 € Compra 3: 300,00 € Bizum: rechazado Tarjeta: rechazado Efectivo: rechazado No se ha podido cobrar
Compruébalo a mano:
- Compra 1 (45 €): Bizum tiene 60 €, así que paga él. Quedan 15 € en Bizum. Sin comisión: 45,00 €.
- Compra 2 (120 €): Bizum solo tiene 15 € y la rechaza. La tarjeta cobra 120 + 1 % = 121,20 €, que cabe en su límite de 150.
- Compra 3 (300 €): Bizum tiene 15 €, la tarjeta ya solo tiene 28,80 € de límite y el efectivo 25 €. Ninguno llega, y
cobrardevuelvenull.
Pasarela.cobrar recibe un Pagable[]. No importa si dentro hay tres clases distintas o veinte: el bucle llama a medio.pagar(...) y Java ejecuta el método del objeto real. Es el polimorfismo con tipo interfaz que viste en la teoría.
Preguntas que te puedes hacer
**¿Por qué comision es default y no abstracto?** Porque la mayoría de medios no cobran comisión. Si fuera abstracto, Bizum y Efectivo estarían obligados a escribir un comision que devuelve 0, y cada medio nuevo tendría que repetirlo. Con default, solo la tarjeta, que es la excepción, lo sobrescribe.
**¿Por qué cobrar recibe Pagable[] y no Tarjeta[]? Porque entonces solo aceptaría tarjetas. Con el tipo interfaz acepta cualquier cosa que cumpla el contrato, hoy y mañana. Es la idea central de las interfaces: programar contra el contrato, no contra la clase**.
**¿Por qué calculamos total antes de llamar a pagar?** Porque pagar devuelve solo un boolean, no cuánto ha cobrado. Para poder imprimir la cantidad con comisión, la calculamos nosotros usando el mismo comision que usa la tarjeta por dentro.
¿Y si el array está vacío o nadie puede pagar? cobrar devuelve null, y el main lo comprueba para escribir «No se ha podido cobrar». Es la forma de decir «no hay resultado» sin lanzar nada.
Usar el resultado de cobrar sin comprobar si es null. Si en la compra 3 escribieras System.out.println(usado.nombre()), el programa se caería con NullPointerException. Siempre que un método pueda devolver null, comprueba antes de usar el resultado.
Pruébalo tú
- Crea una clase
Chequeque implementePagable, con una comisión fija de 2 €, y añádela al array **sin tocarPasarela**. Fíjate en que no hace falta cambiar nada más: ahí está la gracia. - Cambia el orden del array (
Efectivoprimero) y averigua qué medio cobra cada compra. ¿Cambia el resultado de la compra 2?
Programa 2: tienda con carrito
Ahora una tienda que vende tres tipos de producto: libros y camisetas (que se envían) y descargas (que no). El carrito suma los precios, añade los gastos de envío solo de lo físico y se puede pagar con cualquier medio del programa anterior.
La idea
Hay dos capacidades distintas y conviene separarlas:
Vendible: tiene nombre y precio. Todo lo que está en el carrito lo cumple.Enviable: tiene gastos de envío. Solo lo cumplen los productos físicos.
Una descarga es Vendible pero no Enviable; un libro es las dos cosas. Si metiéramos gastosEnvio dentro de Vendible, la descarga tendría que implementar un método que no le corresponde.
Además, precioFinal es un método default que devuelve el precio completo; la camiseta, que tiene un 10 % de descuento, lo sobrescribe.
Para el total, el carrito recorre todos los artículos y suma precioFinal(). Para el envío, pregunta a cada uno si es Enviable con instanceof.
El código
Este programa reutiliza Pagable, Tarjeta y Bizum del anterior (si compilas en la misma carpeta, ya los tienes).
Vendible.java
public interface Vendible {
String nombre();
double precio();
// Por defecto se paga el precio completo; los productos con descuento lo sobrescriben
default double precioFinal() {
return precio();
}
}
Enviable.java
public interface Enviable {
double gastosEnvio();
}
Libro.java
public class Libro implements Vendible, Enviable {
private final String titulo;
private final double precio;
public Libro(String titulo, double precio) {
this.titulo = titulo;
this.precio = precio;
}
@Override
public String nombre() { return titulo; }
@Override
public double precio() { return precio; }
@Override
public double gastosEnvio() { return 3.00; }
}
Camiseta.java
public class Camiseta implements Vendible, Enviable {
private final String modelo;
private final double precio;
public Camiseta(String modelo, double precio) {
this.modelo = modelo;
this.precio = precio;
}
@Override
public String nombre() { return modelo; }
@Override
public double precio() { return precio; }
@Override
public double precioFinal() { // 10 % de descuento
return precio * 0.90;
}
@Override
public double gastosEnvio() { return 4.50; }
}
Descargable.java
public class Descargable implements Vendible { // digital: no es Enviable
private final String titulo;
private final double precio;
public Descargable(String titulo, double precio) {
this.titulo = titulo;
this.precio = precio;
}
@Override
public String nombre() { return titulo; }
@Override
public double precio() { return precio; }
}
Carrito.java
public class Carrito {
private final Vendible[] articulos = new Vendible[10];
private int cantidad = 0;
public void anadir(Vendible v) {
articulos[cantidad++] = v;
}
public double envio() {
double envio = 0;
for (int i = 0; i < cantidad; i++) {
if (articulos[i] instanceof Enviable e) { // solo los productos físicos
envio += e.gastosEnvio();
}
}
return envio;
}
public double total() {
double total = envio();
for (int i = 0; i < cantidad; i++) {
total += articulos[i].precioFinal();
}
return total;
}
public void ticket() {
for (int i = 0; i < cantidad; i++) {
System.out.printf("%s · %.2f €%n", articulos[i].nombre(), articulos[i].precioFinal());
}
System.out.printf("Envío: %.2f €%n", envio());
System.out.printf("TOTAL: %.2f €%n", total());
}
// El carrito no sabe si paga con tarjeta, Bizum o efectivo: solo que es Pagable
public boolean pagar(Pagable medio) {
boolean ok = medio.pagar(total());
System.out.println("Pago con " + medio.nombre() + ": " + (ok ? "aceptado" : "rechazado"));
return ok;
}
}
Tienda.java
public class Tienda {
public static void main(String[] args) {
Carrito carrito = new Carrito();
carrito.anadir(new Libro("Java para FP2", 24.90));
carrito.anadir(new Camiseta("Camiseta SergioLearns", 20.00));
carrito.anadir(new Descargable("Curso de Python", 15.00));
carrito.ticket();
// Se intenta con varios medios hasta que uno funcione
Pagable[] medios = { new Tarjeta(50), new Bizum(100) };
for (Pagable medio : medios) {
if (carrito.pagar(medio)) {
break;
}
}
}
}
Salida
Java para FP2 · 24,90 € Camiseta SergioLearns · 18,00 € Curso de Python · 15,00 € Envío: 7,50 € TOTAL: 65,40 € Pago con Tarjeta: rechazado Pago con Bizum: aceptado
Compruébalo a mano:
- Libro: 24,90 € · Camiseta: 20 € − 10 % = 18,00 € · Descarga: 15,00 €. Suma de productos: 57,90 €.
- Envío: solo el libro (3,00 €) y la camiseta (4,50 €): 7,50 €. La descarga no cuenta.
- Total: 57,90 + 7,50 = 65,40 € ✔.
- Pago: la tarjeta tiene 50 € de límite, y 65,40 € más su comisión no caben → rechazado. Bizum tiene 100 € → aceptado, y el
breakimpide seguir probando.
Preguntas que te puedes hacer
**¿Por qué Enviable es una interfaz aparte y no un método dentro de Vendible?** Porque no todos los vendibles se envían. Una descarga tendría que implementar gastosEnvio() devolviendo 0 o lanzando una excepción, que es justo lo que las interfaces pequeñas y separadas evitan. Cada interfaz, una capacidad.
**¿Por qué usamos instanceof Enviable e y no instanceof Libro || instanceof Camiseta?** Porque mañana puede llegar una Pegatina que también se envíe. Si el carrito pregunta por la capacidad (Enviable), esa nueva clase funciona sin tocar el carrito. Si pregunta por clases concretas, tendrías que acordarte de editarlo cada vez.
**¿Por qué precioFinal es default y no abstracto?** Porque casi todos los productos se venden al precio completo. Solo la camiseta lo sobrescribe para aplicar el descuento. El resto lo hereda sin escribir nada.
**¿Por qué Carrito.pagar recibe un Pagable?** Para que el carrito no sepa si se paga con tarjeta, Bizum o efectivo, y funcione igual con cualquier medio presente o futuro. El carrito depende del contrato, no de ninguna clase concreta.
El array del carrito tiene 10 huecos y anadir no comprueba nada. Si añades el undécimo producto, el programa se cae con ArrayIndexOutOfBoundsException. Cuando uses arrays de tamaño fijo, protege las entradas (o comprueba cantidad < articulos.length antes de añadir).
Pruébalo tú
- Crea una clase
Pegatinaque seaVendibleyEnviable(precio 2 €, envío 1 €) y añádela a la tienda **sin modificarCarrito**. Comprueba que el envío y el total se actualizan solos. - Haz que
anadirdevuelva unboolean:truesi cabía yfalsesi el carrito estaba lleno. Prueba a meter 11 productos.
Programa 3: mando de dispositivos
El último programa es el mando del que te hablaba al principio. Tenemos tres dispositivos: una linterna (solo se enciende y se apaga), un altavoz (además reproduce música) y una televisión (además se conecta a una red y no reproduce nada hasta que la tiene). El mando los controla a todos con las mismas órdenes y cada dispositivo reacciona solo a lo que sabe hacer.
La idea
Tres capacidades, tres interfaces:
Encendible: se enciende, se apaga y dice si está encendido.Reproducible: reproduce una pista y dice cuál suena.Conectable: se conecta a una red y dice a cuál.
Cada dispositivo elige su combinación: la linterna implementa una, el altavoz dos y la televisión las tres. Algo que con clases abstractas era imposible.
Encendible y Reproducible tienen ambas un método default estado(). En las clases que implementan las dos hay que resolver el choque, combinando lo que dice cada una.
El Mando guarda un Encendible[], porque todo se enciende y se apaga. Para las órdenes que no son de todos (conectar, reproducir) pregunta primero con instanceof.
El código
Encendible.java
public interface Encendible {
String nombre();
void encender();
void apagar();
boolean estaEncendido();
default String estado() {
return estaEncendido() ? "encendido" : "apagado";
}
}
Reproducible.java
public interface Reproducible {
void reproducir(String pista);
String pistaActual(); // null si no suena nada
default String estado() {
return pistaActual() == null ? "en silencio" : "sonando \"" + pistaActual() + "\"";
}
}
Conectable.java
public interface Conectable {
void conectar(String red);
String redActual(); // null si no hay red
}
Linterna.java
public class Linterna implements Encendible { // solo se enciende y se apaga
private boolean encendida;
@Override
public String nombre() { return "Linterna"; }
@Override
public void encender() { encendida = true; }
@Override
public void apagar() { encendida = false; }
@Override
public boolean estaEncendido() { return encendida; }
}
Altavoz.java
public class Altavoz implements Encendible, Reproducible {
private boolean encendido;
private String pista;
@Override
public String nombre() { return "Altavoz"; }
@Override
public void encender() { encendido = true; }
@Override
public void apagar() {
encendido = false;
pista = null;
}
@Override
public boolean estaEncendido() { return encendido; }
@Override
public void reproducir(String pista) {
if (encendido) {
this.pista = pista;
}
}
@Override
public String pistaActual() { return pista; }
// Las dos interfaces tienen un default estado(): hay que decidir cómo combinarlos
@Override
public String estado() {
return Encendible.super.estado() + ", " + Reproducible.super.estado();
}
}
Television.java
public class Television implements Encendible, Reproducible, Conectable {
private boolean encendida;
private String pista;
private String red;
@Override
public String nombre() { return "Televisión"; }
@Override
public void encender() { encendida = true; }
@Override
public void apagar() {
encendida = false;
pista = null;
}
@Override
public boolean estaEncendido() { return encendida; }
@Override
public void conectar(String red) { this.red = red; }
@Override
public String redActual() { return red; }
@Override
public void reproducir(String pista) {
if (encendida && red != null) { // necesita estar encendida Y con red
this.pista = pista;
}
}
@Override
public String pistaActual() { return pista; }
@Override
public String estado() {
return Encendible.super.estado() + ", " + Reproducible.super.estado()
+ ", red: " + (red == null ? "sin conectar" : red);
}
}
Mando.java
public class Mando {
private final Encendible[] dispositivos;
public Mando(Encendible[] dispositivos) {
this.dispositivos = dispositivos;
}
public void encenderTodo() {
for (Encendible d : dispositivos) {
d.encender();
}
}
public void apagarTodo() {
for (Encendible d : dispositivos) {
d.apagar();
}
}
// Solo los que además son Conectable / Reproducible reciben la orden
public void conectarTodo(String red) {
for (Encendible d : dispositivos) {
if (d instanceof Conectable c) {
c.conectar(red);
}
}
}
public void sonar(String pista) {
for (Encendible d : dispositivos) {
if (d instanceof Reproducible r) {
r.reproducir(pista);
}
}
}
public void mostrar() {
for (Encendible d : dispositivos) {
System.out.println(" " + d.nombre() + ": " + d.estado());
}
}
public static void main(String[] args) {
Mando mando = new Mando(new Encendible[] { new Linterna(), new Altavoz(), new Television() });
System.out.println("Todo encendido, sin red:");
mando.encenderTodo();
mando.sonar("Bohemian Rhapsody");
mando.mostrar();
System.out.println("Con la red conectada:");
mando.conectarTodo("WiFi-Casa");
mando.sonar("Bohemian Rhapsody");
mando.mostrar();
System.out.println("Apagado:");
mando.apagarTodo();
mando.mostrar();
}
}
Salida
Todo encendido, sin red: Linterna: encendido Altavoz: encendido, sonando "Bohemian Rhapsody" Televisión: encendido, en silencio, red: sin conectar Con la red conectada: Linterna: encendido Altavoz: encendido, sonando "Bohemian Rhapsody" Televisión: encendido, sonando "Bohemian Rhapsody", red: WiFi-Casa Apagado: Linterna: apagado Altavoz: apagado, en silencio Televisión: apagado, en silencio, red: WiFi-Casa
Compruébalo a mano:
- Todo encendido, sin red: el altavoz suena enseguida; la tele está encendida, pero sin red no reproduce nada, por eso dice «en silencio».
- Con la red conectada: la orden
conectarTodosolo llega a la tele (la únicaConectable). Al volver a pedir la pista, ahora sí suena. La linterna no cambia nada: no sabe reproducir ni conectarse. - Apagado:
apagarborra la pista, así que el altavoz y la tele quedan «en silencio». La tele conserva su red, que es otro dato.
Preguntas que te puedes hacer
**¿Por qué Altavoz tiene que sobrescribir estado()?** Porque implementa dos interfaces con un default estado() y Java no sabe cuál elegir. Con Encendible.super.estado() y Reproducible.super.estado() llamamos a cada versión y las juntamos con una coma.
**¿Por qué Mando guarda Encendible[] y no un array de Object?** Porque con Encendible el mando puede llamar a encender() y apagar() directamente. Con Object tendrías que preguntar por el tipo en todas partes. El tipo interfaz dice justo lo que necesitas ver.
**¿Por qué conectarTodo pregunta instanceof Conectable c?** Porque no todos los dispositivos se conectan. La pregunta evita llamar a un método que la linterna ni conoce, y además deja el mando listo para cualquier dispositivo nuevo que algún día sea Conectable.
¿Por qué la televisión necesita red para sonar? Es una regla de su reproducir, no del contrato: cada clase decide cómo cumple lo que promete. La interfaz solo dice que existe reproducir; lo que hace por dentro es cosa de cada clase.
Si quitas el método estado() de Altavoz, el programa no compila: «types Encendible and Reproducible are incompatible; class Altavoz inherits unrelated defaults for estado()…». Dos default con el mismo nombre en dos interfaces te obligan a decidir en la clase.
Pruébalo tú
- Añade una interfaz
ConVolumenconsubir()ybajar(), hazla implementar por el altavoz y la televisión, y añade un métodosubirVolumen()al mando que solo afecte a los dispositivos que la cumplan. - Crea una
Lamparaque implemente soloEncendibley añádela al mando. ¿Tienes que cambiar algo enMando?
Errores típicos en esta práctica
- **Olvidar
public** al sobrescribir un método de la interfaz: «attempting to assign weaker access privileges; was public». - Dejar un método sin implementar: «is not abstract and does not override abstract method…». El mensaje te dice cuál falta.
- Usar un método propio de la clase a través de una variable de tipo interfaz: «cannot find symbol». Antes,
instanceofy conversión. - **No resolver el choque de dos
default** con el mismo nombre en una clase que implementa ambas interfaces. - Hacer un cast sin comprobar con
instanceof, que puede acabar enClassCastException. - **Usar el resultado de un método que puede devolver
null** sin comprobarlo.
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 una versión reducida del carrito del programa 2, con una variable de tipo interfaz, un instanceof y la comprobación de una segunda capacidad. Está escrita con el instanceof y el cast clásicos (sin pattern matching), que es más fácil de seguir.
public class Tutor {
interface Vendible {
double precio();
}
interface Enviable {
double envio();
}
static class Libro implements Vendible, Enviable {
public double precio() { return 24.90; }
public double envio() { return 3.00; }
}
static class Descargable implements Vendible {
public double precio() { return 15.00; }
}
public static void main(String[] args) {
Vendible[] carrito = { new Libro(), new Descargable() };
double total = 0;
for (Vendible v : carrito) {
total += v.precio();
if (v instanceof Enviable) {
Enviable e = (Enviable) v;
total += e.envio();
}
}
System.out.println("Total: " + total);
}
}
Salida esperada:
Total: 42.9
Qué observar paso a paso:
- En
Vendible[] carrito = ..., fíjate en que el array contiene dos referencias a objetos distintos, unLibroy unDescargable. Aunque el array es de tipoVendible, los objetos conservan su clase real. - En la primera vuelta,
total += v.precio()entra en elprecio()de **Libro**. Después, la condiciónv instanceof Enviablees verdadera: observa cómo aparece una variable nuevaeque apunta al mismo objeto quev(dos nombres, un objeto). - Al ejecutar
total += e.envio(),totalpasa de 24,9 a 27,9: el libro se envía. - En la segunda vuelta,
vapunta alDescargable. La condiciónv instanceof Enviableda falso, así que se salta el bloque y no se declarae. El total final sale 42,9.
Resumen
- Una interfaz define un contrato de capacidades; una clase puede cumplir varias a la vez, lo que con la herencia es imposible.
- Las variables, parámetros y arrays de tipo interfaz permiten escribir código que funciona con cualquier clase presente o futura que cumpla el contrato.
defaultevita repetir lo que casi todos hacen igual; quien sea distinto, lo sobrescribe.instanceofpregunta por una capacidad antes de usarla; así el código no depende de clases concretas.- Si dos interfaces traen el mismo
default, la clase decide conInterfaz.super.metodo(). - Una interfaz pequeña y específica (
Enviable) es mejor que una grande llena de métodos que no todos usan.
Checklist: ¿lo has practicado?
- [ ] He compilado y ejecutado los tres programas.
- [ ] He comprobado a mano al menos una salida de cada programa.
- [ ] He añadido una clase nueva (
Cheque,PegatinaoLampara) sin tocar el código que la usa. - [ ] He provocado el error de
defaultduplicado y sé arreglarlo conInterfaz.super. - [ ] He probado
instanceofcon interfaces y entiendo por qué es mejor que preguntar por clases concretas. - [ ] He mirado el programa de Python Tutor y he visto dos variables apuntando al mismo objeto.
Y ahora, a por los ejercicios: ejercicios de interfaces, con tres niveles, tres fragmentos para depurar y un chuletario al final. ¡Nos vemos allí!
Siguiente paso: ejerciciosInterfaces en Java — ejercicios con figuras, una biblioteca y sensores (y el cast que funcionó hasta que llegó una camiseta)¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
