Interfaces en Java

Interfaces en Java

1. Introducción

En este tutorial, vamos a hablar sobre las interfaces en Java. También veremos cómo Java las utiliza para implementar polimorfismo y la herencia múltiple.

2. ¿Qué Son las Interfaces en Java?

En Java, una interfaz es un tipo abstracto que contiene una colección de métodos y variables constantes. Es uno de los conceptos fundamentales en Java y se utiliza para lograr la abstracción, polimorfismo y la herencia múltiple.

Veamos un ejemplo simple de una interfaz en Java:

public interface Electronico {

    // Variable constante
    String LED = "LED";

    // Método abstracto
    int getConsumoDeElectricidad();

    // Método estático
    static boolean esEficienteDesdeElPuntoDeVistaEnergetico(String tipoElectronico) {
        if (tipoElectronico.equals(LED)) {
            return true;
        }
        return false;
    }

    // Método predeterminado
    default void imprimirDescripcion() {
        System.out.println("Descripción Electrónica");
    }
}

Podemos implementar una interfaz en una clase Java utilizando la palabra clave implements.

A continuación, creemos también una clase Computadora que implemente la interfaz Electronico que acabamos de crear:

public class Computadora implements Electronico {

    @Override
    public int getConsumoDeElectricidad() {
        return 1000;
    }
}

2.1 Reglas para Crear Interfaces

En una interfaz, se nos permite usar:

También debemos recordar que:

  • No podemos instanciar interfaces directamente.
  • Una interfaz puede estar vacía, sin métodos ni variables.
  • No podemos usar la palabra clave final en la definición de la interfaz, ya que dará como resultado un error de compilación.
  • Todas las declaraciones de interfaz deben tener el modificador de acceso public o default; el modificador abstract se agregará automáticamente por el compilador.
  • Un método de interfaz no puede ser protected o final.
  • Hasta Java 9, los métodos de interfaz no podían ser privados; sin embargo, Java 9 introdujo la posibilidad de definir métodos privados en interfaces.
  • Las variables de interfaz son public, static y final por definición; no se nos permite cambiar su visibilidad.

3. ¿Qué Podemos Lograr Usándolas?

3.1 Funcionalidad de Comportamiento

Utilizamos interfaces para agregar cierta funcionalidad de comportamiento que puede ser utilizada por clases no relacionadas. Por ejemplo, Comparable, Comparator y Cloneable son interfaces de Java que pueden ser implementadas por clases no relacionadas. A continuación, se muestra un ejemplo de la interfaz Comparator que se utiliza para comparar dos instancias de la clase Empleado:

public class Empleado {

    private double salario;

    public double getSalario() {
        return salario;
    }

    public void setSalario(double salario) {
        this.salario = salario;
    }
}

public class ComparadorDeSalarioEmpleado implements Comparator<Empleado> {

    @Override
    public int compare(Empleado empleadoA, Empleado empleadoB) {
        if (empleadoA.getSalario() < empleadoB.getSalario()) {
            return -1;
        } else if (empleadoA.getSalario() > empleadoB.getSalario()) { 
            return 1;
        } else {
            return 0;
        }
    }
}

3.2 Herencia Múltiple

Las clases Java admiten la herencia singular. Sin embargo, mediante el uso de interfaces, también podemos implementar la herencia múltiple.

Por ejemplo, en el siguiente ejemplo, notamos que la clase Coche implementa las interfaces Volar y Transformar. Al hacerlo, hereda los métodos volar y transformar:

public interface Transformar {
    void transformar();
}

public interface Volar {
    void volar();
}

public class Coche implements Volar, Transformar {

    @Override
    public void volar() {
        System.out.println("¡Puedo Volar!");
    }

    @Override
    public void transformar() {
        System.out.println("¡Puedo Transformarme!");
    }
}

3.3 Polimorfismo

Comencemos haciendo la pregunta: ¿qué es el polimorfismo? Es la capacidad de un objeto para tomar diferentes formas durante la ejecución. Para ser más específicos, es la ejecución del método sobrescrito que está relacionado con un tipo de objeto específico en tiempo de ejecución.

En Java, podemos lograr el polimorfismo utilizando interfaces. Por ejemplo, la interfaz Forma puede tomar diferentes formas: puede ser un Circulo o un Cuadrado.

Comencemos definiendo la interfaz Forma:

public interface Forma {
    String nombre();
}

Ahora también creemos la clase Circulo:

public class Círculo implements Forma {

    @Override
    public String nombre() {
        return "Círculo";
    }
}

Y también la clase Cuadrado:

public class Cuadrado implements Forma {

    @Override
    public String nombre() {
        return "Cuadrado";
    }
}

Finalmente, es hora de ver el polimorfismo en acción utilizando nuestra interfaz Forma y sus implementaciones. Creemos algunas instancias de objetos Forma, agréguelas a una List y, finalmente, imprima sus nombres en un bucle:

List<Forma> formas = new ArrayList<>();
Forma formaCirculo =

 new Círculo();
Forma formaCuadrado = new Cuadrado();

formas.add(formaCirculo);
formas.add(formaCuadrado);

for (Forma forma : formas) {
    System.out.println(forma.nombre());
}

4. Métodos default en Interfaces

Las interfaces tradicionales en Java 7 y anteriores no ofrecen compatibilidad inversa.

Lo que esto significa es que si tienes código heredado escrito en Java 7 o anterior, y decides agregar un método abstract a una interfaz existente, entonces todas las clases que implementen esa interfaz deben sobrescribir el nuevo método abstract. De lo contrario, el código se romperá.

Java 8 resolvió este problema al introducir el método default, que es opcional y puede implementarse a nivel de la interfaz.

5. Reglas de Herencia de Interfaces

Para lograr la herencia múltiple a través de interfaces, debemos recordar algunas reglas. Veamos estas en detalle.

5.1 Interfaz que Extiende Otra Interfaz

Cuando una interfaz extiende (extends) otra interfaz, hereda todos los métodos abstractos de esa interfaz. Comencemos creando dos interfaces, TieneColor y Forma:

public interface TieneColor {
    String obtenerColor();
}

public interface Caja extends TieneColor {
    int obtenerAltura();
}

En el ejemplo anterior, Caja hereda de TieneColor utilizando la palabra clave extends. Al hacerlo, la interfaz Caja hereda obtenerColor. Como resultado, la interfaz Caja ahora tiene dos métodos: obtenerColor y obtenerAltura.

5.2 Clase Abstracta que Implementa una Interfaz

Cuando una clase abstracta implementa una interfaz, hereda todos sus métodos abstract y default. Consideremos la interfaz Transformar y la clase abstracta Vehículo que la implementa:

public interface Transformar {
    
    void transformar();

    default void imprimirEspecificaciones(){
        System.out.println("Especificaciones de Transformación");
    }
}

public abstract class Vehículo implements Transformar {}

En este ejemplo, la clase Vehículo hereda dos métodos: el método abstracto transformar y el método default imprimirEspecificaciones.

6. Interfaces Funcionales

Java ha tenido muchas interfaces funcionales desde sus primeros días, como Comparable (desde Java 1.2) y Runnable (desde Java 1.0).

Java 8 introdujo nuevas interfaces funcionales como Predicate, Consumer y Function. Para obtener más información sobre estas, visita nuestro tutorial sobre Interfaces Funcionales en Java 8 en futuras lecciones.

7. Conclusión

En este tutorial, ofrecimos una visión general de las interfaces en Java y hablamos sobre cómo usarlas para lograr polimorfismo y la herencia múltiple.