Ir al contenido
wannacode

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();
}
Una clase que acepta el contrato implementa la interfaz y escribe cada uno de sus métodos:
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() + " €");
Salida
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 + " €");
Salida
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
  1. El array es de tipo Payable. Las flechas apuntan a tres clases, pero solo llegan a su contrato.
  2. total empieza en 0.
  3. p apunta al empleado: amountDue() devuelve el salario, 2000.
  4. El gerente es un empleado, así que también se le puede pagar. Salario más bono: 3500.
  5. La factura no es un empleado, pero cumple el mismo contrato: 340.
  6. 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());
Respuesta

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());
}
Salida
Compañía eléctrica;340
Casero;1800

Si 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

Esta factura no compila. ¿Qué línea tiene el error?

Elige la línea con el error.

Pruébalo

¿Qué falta?

class Invoice  Payable {
    // ...
}

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».
A menudo se usan juntas: 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();
}
Salida
El pájaro bate las alas
Planea por el aire
El avión despega
Planea 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.

Salida
¡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[].

Salida
Nombre: Jorge, edad: 45
Nombre: Elena, edad: 19
Nota media: 5.5

3. Baile. Una interfaz Danceable con un método dance(). DanceMove (un nombre y una duración) la implementa.

Salida
Bailo salsa durante 30 segundos

4. 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.

Salida
No hay ninguna canción elegida
Reproduzco 'Bohemian Rhapsody'

5. El estanque. Interfaces Swimmable con swim() y Flyable con fly(). Fish nada. Duck nada y vuela.

Salida
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 en Subscription (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 Employee también implemente Exportable, con una línea «nombre;salario mensual». Pon empleados y facturas en un solo array Exportable[].

Test

Ponte a prueba

1. ¿Cuántas interfaces puede implementar una clase?
2. ¿Compila new Payable()?
3. Una clase implementa un método de la interfaz sin public. ¿Qué pasa?
4. En Java, ¿cómo dices que una clase implementa una interfaz?
5. Hay que pagar a los empleados y las facturas en un solo bucle. No comparten código. ¿Qué encaja mejor?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·