Original article: Java Interfaces Explained with Examples

La interfaz en Java es un poco como la Clase, pero con una diferencia significativa: una interface puede tener firmas de métodos, campos y métodos predeterminados. Desde Java 8, también puedes crear métodos predeterminados. En el siguiente bloque puedes ver un ejemplo de interfaz:

public interface Vehiculo {
    public String matricula = "";
    public float maxVel
    public void arrancar();
    public void detener();
    default void claxon(){
      System.out.println("Sonando claxon");
   }
}

La interfaz anterior contiene dos campos, dos métodos y un método predeterminado. Solo, no es de mucha utilidad, pero normalmente se usan junto con las Clases. ¿Cómo? Simple, tienes que asegurarte de que alguna clase lo implemente (implements).

public class Coche implements Vehiculo {
    public void arrancar() {
        System.out.println("arrancando motor...");
    }
    public void detener() {
        System.out.println("deteniendo motor...");
    }
}

Ahora, hay una regla básica: la clase debe implementar todos los métodos que hay en la interfaz. Los métodos deben tener exactamente la misma firma (nombre, parámetros y excepciones) como se describe en la interfaz. Sin embargo, la clase no necesita declarar los campos, solo los métodos.

Instancias de una interfaz

Una vez que creas una clase Java que implements (implementa) cualquier interfaz, se puede hacer referencia a la instancia del objeto como una instancia de la interfaz. Este concepto es similar al de creación de instancias de herencia.

// siguiendo nuestro ejemplo anterior

Vehiculo tesla = new Coche();

tesla.arrancar(); // arrancar el motor...

Una interfaz no puede contener métodos constructores. Por lo tanto, no puedes crear una instancia de una interfaz en sí. Debes crear una instancia de alguna clase que implemente una interfaz para hacer referencia a ella.

Piensa en las interfaces como un formulario de contrato en blanco o una plantilla.

¿Qué puedes hacer con esta función? ¡Polimorfismo! ¡Puedes usar solo interfaces para referirte a instancias de objetos!

class Camion implements Vehiculo {
    public void arrancar() {
        System.out.println("arrancando el motor del camión...");
    }
    public void detener() {
        System.out.println("deteniendo el motor del camión...");
    }
}

class Arrancador {
    // método estático, se puede llamar sin instanciar la clase
    public static void arrancarMotor(Vehiculo vehiculo) {
        vehiculo.arrancar();
    }
}

Vehiculo tesla = new Coche();
Vehiculo tata = new Camion();

Arrancador.arrancarMotor(tesla); // arrancando motor...
Arrancador.arrancarMotor(tata); // arrancar el motor del camión...

Pero, ¿qué hay de las múltiples interfaces?

Sí, puedes implementar varias interfaces en una sola clase. Mientras estaba en Herencia dentro de las clases estaba restringido a heredar solo una clase, aquí puedes ampliar cualquier cantidad de interfaces. Pero no olvides implementar todos los métodos de todas las interfaces, de lo contrario, la compilación fallará.

public interface GPS {
    public void obtenerCoordenadas();
}

public interface Radio {
    public void iniciarRadio();
    public void detenerRadio();
}

public class Smartphone implements GPS,Radio {
    public void obtenerCoordenadas() {
        // devuelve algunas coordenadas
    }
    public void iniciarRadio() {
      // iniciar Radio
    }
    public void detenerRadio() {
        // detener Radio
    }
}

Algunas características de las interfaces

  • Puedes colocar variables dentro de una interfaz, aunque no será una decisión sensata, ya que las clases no están obligadas a tener la misma variable. En resumen, ¡evita colocar variables!
  • Todas las variables y métodos en una interfaz son públicos, incluso si omites la palabra clave public.
  • Una interfaz no puede especificar la implementación de un método en particular. Depende de las Clases hacerlo. Aunque ha habido una excepción reciente (ver más abajo).
  • Si una clase implementa varias interfaces, existe una posibilidad remota de superposición de la firma del método. Dado que Java no permite múltiples métodos de la misma firma exacta, podría generar problemas. Consulta esta pregunta para obtener más información.

Métodos predeterminados de la interfaz

Antes de Java 8, no teníamos forma de dirigir una interfaz para que tuviera una implementación de método particular, lo que genera mucha confusión y roturas de código si la definición de una interfaz cambia repentinamente.

Pongamos que escribes una biblioteca de código abierto, que contiene una interfaz. Digamos que tus clientes, es decir, prácticamente todos los desarrolladores de todo el mundo, lo están usando mucho y están contentos. Ahora has tenido que actualizar la biblioteca agregando una nueva definición de método a la interfaz para admitir una nueva característica. Pero eso rompería todas las compilaciones, ya que todas las clases que implementan esa interfaz tienen que cambiar ahora. ¡Qué catástrofe!

Afortunadamente, Java 8 ahora nos proporciona métodos default (predeterminados) para interfaces. ¡Un método default puede contener su propia implementación directamente dentro de la interfaz! Entonces, si una Clase no implementa un método predeterminado, el compilador tomará la implementación mencionada dentro de la Interfaz. Bonito, ¿no? Entonces, en tu biblioteca, puede agregar cualquier cantidad de métodos predeterminados en las interfaces sin miedo a estropear nada.

public interface GPS {
    public void obtenerCoordenadas();
    default public void obtenerCoordenadasAproximadas() {
        // implementación para devolver coordenadas de fuentes aproximadas
        // como wifi y móvil
        System.out.println("Obteniendo coordenadas aproximadas por wifi y GSM...");
    }
}

public interface Radio {
    public void iniciarRadio();
    public void detenerRadio();
}

public class Smartphone implements GPS,Radio {
    public void obtenerCoordenadas() {
        // devuelve algunas coordenadas
    }
    public void iniciarRadio() {
      // iniciar Radio
    }
    public void detenerRadio() {
        // detener Radio
    }

    // sin implementación de getRoughCoordinates()
}

Smartphone motoG = new Smartphone();
motog.obtenerCoordenadasAproximadas(); // Obteniendo coordenadas aproximadas por wifi y GSM...

Pero, ¿qué sucede si dos interfaces tienen la misma firma de método?

Gran pregunta. En ese caso, si no proporcionas la implementación en la clase, el pobre compilador se confundirá y simplemente fallará. También debes proporcionar una implementación de método predeterminada dentro de la Clase. También hay una forma ingeniosa de usar super para llamar a la implementación que te gusta:

public interface Radio {
    // public void iniciarRadio();
    // public void detenerRadio();

    default public void siguiente() {
        System.out.println("Siguiente emisora de Radio");
    }
}

public interface ReproductorMusica {
    // public void iniciar();
    // public void pausar();
    // public void detener();

    default public void siguiente() {
        System.out.println("Siguiente canción del Reproductor de Música");
    }
}

public class Smartphone implements Radio, ReproductorMusica {
    public void siguiente() {
        // Supongamos que deseas llamar a siguiente del ReproductorMusica
        ReproductorMusica.super.siguiente();
    }
}

Smartphone motoG = new Smartphone();
motoG.siguiente();  // Siguiente desde ReproductorMusica

Métodos estáticos en interfaces

Otra novedad de Java 8 es la capacidad de agregar métodos estáticos a las interfaces. Los métodos estáticos en interfaces son casi idénticos a los métodos estáticos en clases concretas. La única gran diferencia es que los métodos static no se heredan en las clases que implementan la interfaz. Esto significa que se hace referencia a la interfaz cuando se llama al método estático, no a la clase que lo implementa.

interface ReproductorMusica {
  public static void comercial(String patrocinador) {
    System.out.println("Nuevo mensaje de " + patrocinador);
  }
  
  public void play();
}


class Smartphone implements ReproductorMusica {
	public void reproducir() {
		System.out.println("Reproduciendo desde el Smartphone");
	}
}

class Main {
  public static void main(String[] args) {
    Smartphone motoG = new Smartphone();
    ReproductorMusica.comercial("Motorola"); 
    // Llamada a la interfaz no a la clase implementada
    // motoG.comercial("Motorola");  // Esto causaría un error de compilación
  }
}

Heredar una interfaz

También es posible en Java que una interfaz herede otra interfaz usando, la palabra clave extends:

public interface Reproductor {
    public void iniciar();
    public void pausar();
    public void detener();
}

public interface ReproductorMusica extends Reproductor {
    default public void siguiente() {
        System.out.println("siguiente canción del Reproductor de Música");
    }
}

Eso significa que la clase que implementa ReproductorMusica tiene que implementar todos los métodos de ReproductorMusica así como los de Reproductor:

public class SmartPhone implements ReproductorMusica {
    public void iniciar() {
        System.out.println("iniciar");
    }
    public void detener() {
        System.out.println("detener");
    }
    public void pausar() {
        System.out.println("pausar");
    }
}

¡Ahora ya tienes una buena comprensión de las interfaces de Java! Obtén información sobre las clases abstractas para ver cómo Java te brinda otra forma de definir contratos.