Saltar al contenido principal

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 Animal y, 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:

Perro es un tipo de Animal.

Mientras que:

class Perro implements Volador

significa:

Perro cumple el contrato de Volador.

--

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);