Interfaces
Hasta ahora hemos aprendido a utilizar la herencia para crear clases a partir de otras clases.
Por ejemplo:
Animal
│
┌────────┴────────┐
↓ ↓
Perro Gato
Tanto Perro como Gato heredan de Animal.
También hemos visto que una clase abstracta puede definir métodos que las clases hijas deben implementar.
Por ejemplo:
public abstract class Animal {
public abstract void hacerSonido();
}
Y después:
public class Perro extends Animal {
@Override
public void hacerSonido() {
System.out.println("Guau");
}
}
Sin embargo, hay situaciones en las que no queremos establecer una relación de herencia entre clases.
Podemos simplemente querer indicar que diferentes clases deben cumplir unas determinadas reglas.
Para estos casos podemos utilizar una interfaz.
Una interfaz permite definir un conjunto de métodos que una clase se compromete a implementar.
Podemos imaginar una interfaz como un contrato:
Interfaz
hacerSonido()
│
┌────────┴────────┐
↓ ↓
Perro Gato
Guau Miau
La interfaz establece:
Las clases que implementen esta interfaz deben proporcionar estos métodos.
Pero cada clase puede decidir cómo hacerlo.
¿Qué es una interfaz?
Una interfaz es una estructura que define qué métodos debe proporcionar una clase.
Para crear una interfaz utilizamos la palabra clave:
interface
Por ejemplo:
public interface Animal {
void hacerSonido();
}
Aquí hemos creado una interfaz llamada Animal.
La interfaz establece que debe existir un método:
void hacerSonido();
Pero no hemos indicado qué debe hacer ese método.
No tenemos:
{
...
}
Solamente:
void hacerSonido();
La interfaz está indicando qué debe existir, pero no cómo debe funcionar.
Podemos imaginarlo así:
Animal
hacerSonido()
│
│
"Debes tener este método"
│
┌──────┴──────┐
↓ ↓
Perro Gato
│ │
Guau Miau
implements
Una clase utiliza la palabra clave implements para indicar que implementa una interfaz.
Por ejemplo:
public class Perro implements Animal {
}
Esto significa:
Perro
│
│ implements
↓
Animal
La clase Perro está diciendo:
Implemento la interfaz
Animaly, por lo tanto, voy a cumplir el contrato que establece.
Como Animal tiene el método:
void hacerSonido();
Perro debe implementarlo.
Por ejemplo:
public class Perro implements Animal {
@Override
public void hacerSonido() {
System.out.println("Guau");
}
}
Y podemos hacer lo mismo con Gato:
public class Gato implements Animal {
@Override
public void hacerSonido() {
System.out.println("Miau");
}
}
Ahora tenemos:
Animal
│
hacerSonido()
│
┌───────┴───────┐
↓ ↓
Perro Gato
│ │
"Guau" "Miau"
La interfaz establece el método y luego cada clase que implemente dicha interfaz decide cómo implementarlo.
Implementar los métodos de una interfaz
Si una clase implementa una interfaz, normalmente debe implementar todos los métodos que esta define.
Por ejemplo:
public interface Vehiculo {
void arrancar();
void parar();
}
La interfaz establece dos métodos:
arrancar()
parar()
Una clase Coche que implemente esta interfaz tendrá que proporcionar ambos:
public class Coche implements Vehiculo {
@Override
public void arrancar() {
System.out.println("El coche ha arrancado");
}
@Override
public void parar() {
System.out.println("El coche se ha parado");
}
}
La interfaz establece:
¿Qué debe existir?
arrancar()
parar()
La clase establece:
¿Cómo funciona?
arrancar() → "El coche ha arrancado"
parar() → "El coche se ha parado"
Interfaces y polimorfismo
Las interfaces están muy relacionadas con el polimorfismo.
Recordemos que el polimorfismo permite utilizar una referencia de un tipo general para trabajar con diferentes objetos.
Por ejemplo:
Volador volador1 = new Pajaro();
Volador volador2 = new Avion();
Las dos variables son de tipo:
Volador
Pero los objetos son diferentes:
volador1
↓
Pajaro
volador2
↓
Avion
Podemos llamar al mismo método:
volador1.volar();
volador2.volar();
Y cada objeto ejecutará su propia implementación.
Por ejemplo:
El pájaro está volando
El avión está volando
Esto es polimorfismo.
La interfaz nos permite trabajar con diferentes clases utilizando un mismo tipo común.
Diferencia entre extends e implements
Es importante no confundir estas dos palabras.
Utilizamos:
extends
cuando una clase hereda de otra clase.
Por ejemplo:
public class Perro extends Animal {
}
Mientras que utilizamos:
implements
cuando una clase implementa una interfaz.
Por ejemplo:
public class Perro implements Volador {
}
Por tanto:
class Perro extends Animal
significa:
Perroes un tipo deAnimal.
Mientras que:
class Perro implements Volador
significa:
Perrocumple el contrato deVolador.
--
Una clase puede implementar varias interfaces
Una característica muy importante de las interfaces es que una clase puede implementar varias interfaces.
Por ejemplo, podemos tener:
public interface Volador {
void volar();
}
Y:
public interface Nadador {
void nadar();
}
Una clase podría implementar ambas:
public class Pato implements Volador, Nadador {
@Override
public void volar() {
System.out.println("El pato está volando");
}
@Override
public void nadar() {
System.out.println("El pato está nadando");
}
}
Ahora Pato debe implementar los métodos de las dos interfaces:
Volador Nadador
│ │
volar() nadar()
│ │
└─────────┬─────────┘
↓
Pato
Podemos utilizar:
Pato pato = new Pato();
pato.volar();
pato.nadar();
Esto es una diferencia importante respecto a la herencia de clases.
En Java, una clase solamente puede heredar directamente de una clase:
public class Pato extends Animal {
}
Pero puede implementar varias interfaces:
public class Pato implements Volador, Nadador {
}
---
Herencia e interfaces al mismo tiempo
Una clase también puede heredar de una clase y, además, implementar una o varias interfaces.
Por ejemplo:
public class Perro extends Animal implements Nadador {
}
Aquí tenemos:
Animal
│
│ extends
↓
Perro
│
│ implements
↓
Nadador
Esto significa que Perro:
- hereda de
Animal - implementa
Nadador
Por ejemplo:
public class Perro extends Animal implements Nadador {
@Override
public void nadar() {
System.out.println("El perro está nadando");
}
}
Métodos con implementación en una interfaz
Hasta ahora hemos visto interfaces con métodos sin implementar:
public interface Volador {
void volar();
}
Sin embargo, las interfaces modernas de Java también pueden tener métodos con implementación.
Uno de los casos más habituales es utilizar:
default
Por ejemplo:
public interface Volador {
void volar();
default void aterrizar() {
System.out.println("El objeto está aterrizando");
}
}
Ahora la interfaz tiene:
volar()
aterrizar()
Una clase que implemente la interfaz tendrá que implementar volar():
public class Avion implements Volador {
@Override
public void volar() {
System.out.println("El avión está volando");
}
}
Pero puede utilizar directamente:
aterrizar()
porque la interfaz ya proporciona una implementación por defecto.
Por ejemplo:
Avion avion = new Avion();
avion.volar();
avion.aterrizar();
Salida:
El avión está volando
El objeto está aterrizando
Constantes en interfaces
Las interfaces también pueden contener valores constantes.
Por ejemplo:
public interface Configuracion {
int MAX_JUGADORES = 4;
}
Podemos acceder a ese valor utilizando el nombre de la interfaz:
System.out.println(Configuracion.MAX_JUGADORES);
Las variables declaradas directamente dentro de una interfaz son constantes.
Por eso normalmente se escriben utilizando mayúsculas:
MAX_JUGADORES
Por ejemplo:
public interface Configuracion {
int MAX_JUGADORES = 4;
int MAX_NIVEL = 100;
}
Podemos utilizar:
System.out.println(Configuracion.MAX_JUGADORES);
System.out.println(Configuracion.MAX_NIVEL);