Interfaces
En resumen: Una interfaz es un contrato: una lista de métodos sin cuerpo que toda clase que la implementa tiene que escribir; una clase puede implementar varias interfaces.
Problema
Salarios y facturas
A final de mes la empresa paga dos tipos de cosas: los salarios de los empleados y las facturas de los proveedores. Quieres un solo array y un solo bucle: a quién se paga, cuánto, y el total al final.
Un array Employee[] no sirve: una factura no es un empleado. Hacer que herede de Employee está mal. La frase «una factura es un empleado» no es cierta, y además la factura tendría un nombre, un salario y un aumento.
Una clase padre común Payment tampoco sirve. Manager ya tiene una clase padre, Employee, y una clase solo puede tener una. Los empleados y las facturas no tienen nada en común, salvo que se pueden pagar.
La idea
Una interfaz: un contrato «puede hacer»
Una interfaz es una lista de métodos sin cuerpo. Dice qué tiene que saber hacer una clase, pero no cómo:
interface Payable { int amountDue();
String payee();}class Invoice implements Payable { private String supplier; private int amount;
public Invoice(String supplier, int amount) { this.supplier = supplier; this.amount = amount; }
@Override public int amountDue() { return amount; }
@Override public String payee() { return supplier; }}
Invoice power = new Invoice("Compañía eléctrica", 340);System.out.println(power.payee() + ": " + power.amountDue() + " €");Compañía eléctrica: 340 €Una interfaz se implementa con implements, no con extends. Aquí también puedes escribir @Override: el compilador comprueba que el método de verdad viene de la interfaz.
Los métodos de una interfaz siempre son public. Son una promesa a todos, así que en la clase también tienen que ser public.
Míralo
Un solo bucle para todo lo que se paga
El empleado también acepta el contrato. Su importe es el salario mensual:
class Employee implements Payable { // ... name, salary, el constructor, monthlyPay()
@Override public int amountDue() { return monthlyPay(); }
@Override public String payee() { return name; }}Ahora tanto los empleados como las facturas son Payable. Una interfaz es un tipo: puede ser el tipo de una variable, de un parámetro o de un array.
Payable[] payments = { new Employee("Juan", 2000), new Manager("María", 3000, 500), new Invoice("Compañía eléctrica", 340), new Invoice("Casero", 1800)};int total = 0;for (Payable p : payments) { System.out.println(p.payee() + ": " + p.amountDue() + " €"); total += p.amountDue();}System.out.println("Total: " + total + " €");Juan: 2000 €María: 3500 €Compañía eléctrica: 340 €Casero: 1800 €Total: 7640 €El gerente no escribe nada nuevo. De Employee hereda tanto el contrato como amountDue(). Y amountDue() llama a monthlyPay(), que el gerente ha sobrescrito. Por eso María recibe 3500 €.
Payable[] payments = { new Employee("Juan", 2000), new Manager("María", 3000, 500), new Invoice("Compañía eléctrica", 340)};int total = 0;for (Payable p : payments) { total += p.amountDue();}System.out.println("Total: " + total + " €");Paso a paso, en palabras
- El array es de tipo Payable. Las flechas apuntan a tres clases, pero solo llegan a su contrato.
- total empieza en 0.
- p apunta al empleado: amountDue() devuelve el salario, 2000.
- El gerente es un empleado, así que también se le puede pagar. Salario más bono: 3500.
- La factura no es un empleado, pero cumple el mismo contrato: 340.
- El bucle no conoce las clases. Solo sabe que a cada uno se le puede pagar.
Pruébalo
¿Cuál es el importe?
¿Qué imprimirá el programa? Las clases son de la tarjeta de arriba.
Payable p = new Manager("María", 3000, 500);System.out.println(p.amountDue());Manager es un Employee, y Employee implementa el contrato. Así que el gerente también lo implementa, sin escribirlo. La llamada va al método sobrescrito del gerente: 3000 + 500.
La idea
Una clase padre, muchos contratos
Contabilidad también quiere las facturas en formato CSV: una línea «proveedor;importe» por cada una. Ese es un segundo contrato. Una clase puede implementar todas las interfaces que quiera, separadas por comas:
interface Exportable { String toCsv();}
class Invoice implements Payable, Exportable { // ... como antes
@Override public String toCsv() { return supplier + ";" + amount; }}Exportable[] forAccounting = { new Invoice("Compañía eléctrica", 340), new Invoice("Casero", 1800) };for (Exportable e : forAccounting) { System.out.println(e.toCsv());}Compañía eléctrica;340Casero;1800Si la clase tiene a la vez una clase padre y interfaces, la clase padre va primero:
class Manager extends Employee implements Exportable {Hay una sola clase padre y muchos contratos. Así, una clase cuya clase padre ya está ocupada puede entrar igualmente en un array común con clases completamente distintas.
Pruébalo
Encuentra el error
Elige la línea con el error.
public. Los métodos de una interfaz se prometen a todos, y sin modificador el método queda oculto. El compilador dice: "attempting to assign weaker access privileges; was public".Pruébalo
¿Qué falta?
class Invoice Payable {
// ...
}implements. Para una clase padre es extends, para una interfaz es implements.La idea
¿Clase abstracta o interfaz?
Las dos son clases padre de las que no puedes crear objetos, y las dos ponen obligaciones a las clases de debajo. La diferencia está en lo que dicen:
| Clase abstracta | Interfaz | |
|---|---|---|
| Dice | «X es un Y» | «X puede» |
| Cuántas por clase | una | todas las que quieras |
| Campos y constructor | sí | no |
| Código común | sí | casi nada |
- Una clase abstracta, cuando las clases de debajo son un mismo tipo de cosa y comparten código y datos: las figuras tienen un nombre y una descripción, los empleados tienen un nombre y un salario.
- Una interfaz, cuando las clases son distintas pero necesitan una capacidad común: un empleado y una factura «se pueden pagar», un pájaro y un avión «pueden volar».
Employee es la clase padre del gerente y del becario, y a la vez implementa Payable.Más precisamente, para curiosos
- Desde Java 8 una interfaz puede tener un método por defecto (
default), con cuerpo. Las clases lo reciben ya hecho, y también lo pueden sobrescribir. Así puedes añadir un método nuevo a una interfaz sin romper las clases que ya la implementan:
interface Flyable { void fly();
default void glide() { System.out.println("Planea por el aire"); }}
class Bird implements Flyable { @Override public void fly() { System.out.println("El pájaro bate las alas"); }}
class Plane implements Flyable { @Override public void fly() { System.out.println("El avión despega"); }}
Flyable[] flyers = { new Bird(), new Plane() };for (Flyable f : flyers) { f.fly(); f.glide();}El pájaro bate las alasPlanea por el aireEl avión despegaPlanea por el aire- Una interfaz no puede tener campos de objeto. Puede tener constantes: todo campo en ella es
public static final.
Ejercicio
Contratos
Resuelve las tareas en tu editor.
1. Claxon. Una interfaz Honkable con un método honk(). SimpleCar del módulo «Clases y objetos» la implementa.
¡Bip, bip!2. Datos. Una interfaz Printable con un método printDetails(). Impleméntala en Person, y haz que Student (una clase hija de Person) la sobrescriba y añada la nota. Pon los dos en un array Printable[].
Nombre: Jorge, edad: 45Nombre: Elena, edad: 19Nota media: 5.53. Baile. Una interfaz Danceable con un método dance(). DanceMove (un nombre y una duración) la implementa.
Bailo salsa durante 30 segundos4. DJ. Una clase Song con un título. Una interfaz Playable con un método play(). DJ la implementa y tiene un método playSong(Song song) que recuerda la canción y la reproduce. Si todavía no hay canción, play() lo dice.
No hay ninguna canción elegidaReproduzco 'Bohemian Rhapsody'5. El estanque. Interfaces Swimmable con swim() y Flyable con fly(). Fish nada. Duck nada y vuela.
El pez nada.El pato nada.El pato vuela.Más tareas
- Descuento. Una interfaz Discountable con un método que devuelve el precio después del descuento. Impleméntala en
Product(10 % de descuento) y enSubscription(25 % de descuento para estudiantes). Calcula el total de un carrito con un producto de 50 € y una suscripción de estudiante de 12 €: 45 € + 9 € = 54 €. - CSV para todos. Haz que
Employeetambién implemente Exportable, con una línea «nombre;salario mensual». Pon empleados y facturas en un solo array Exportable[].
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Encapsulamiento · Herencia · Polimorfismo · Clases abstractas
- Continúa en: Comprobaciones de tipo · Principios de la POO · Lanzar excepciones y crear las tuyas · Archivos
- Ejercicios sobre el tema: Herencia y polimorfismo