Buscar..


Introducción

Una interfaz es un tipo de referencia, similar a una clase, que se puede declarar utilizando la palabra clave de la interface . Las interfaces solo pueden contener constantes, firmas de métodos, métodos predeterminados, métodos estáticos y tipos anidados. Los cuerpos de los métodos existen solo para los métodos predeterminados y los métodos estáticos. Al igual que las clases abstractas, las interfaces no pueden ser instanciadas, solo pueden implementarse por clases o extenderse por otras interfaces. La interfaz es una forma común de lograr una abstracción completa en Java.

Sintaxis

  • interfaz pública Foo {void foo (); / * cualquier otro método * /}
  • interfaz pública Foo1 extiende Foo {void bar (); / * cualquier otro método * /}
  • la clase pública Foo2 implementa Foo, Foo1 {/ * implementación de Foo y Foo1 * /}

Declarar e implementar una interfaz

Declaración de una interfaz usando la palabra clave de la interface :

public interface Animal {
    String getSound(); // Interface methods are public by default
}

Anular anotación

@Override
public String getSound() {
    // Code goes here...
}

Esto obliga al compilador a verificar que estamos anulando e impide que el programa defina un nuevo método o arruine la firma del método.

Las interfaces se implementan utilizando la palabra clave implements .

public class Cat implements Animal {

    @Override 
    public String getSound() {
        return "meow";
    }
}

public class Dog implements Animal {

    @Override
    public String getSound() {
        return "woof";
    }
}

En el ejemplo, las clases Cat y Dog deben definir el método getSound() , ya que los métodos de una interfaz son inherentemente abstractos (con la excepción de los métodos predeterminados).

Usando las interfaces

Animal cat = new Cat();
Animal dog = new Dog();

System.out.println(cat.getSound()); // prints "meow"
System.out.println(dog.getSound()); // prints "woof"

Implementando multiples interfaces

Una clase de Java puede implementar múltiples interfaces.

public interface NoiseMaker {
    String noise = "Making Noise"; // interface variables are public static final by default

    String makeNoise(); //interface methods are public abstract by default
}

public interface FoodEater {
    void eat(Food food);
}

public class Cat implements NoiseMaker, FoodEater { 
    @Override
    public String makeNoise() {
        return "meow";
    }

    @Override
    public void eat(Food food) {
        System.out.println("meows appreciatively");
    }
}

Observe cómo la clase Cat debe implementar los métodos abstract heredados en ambas interfaces. Además, observe cómo una clase puede implementar prácticamente tantas interfaces como sea necesario (hay un límite de 65,535 debido a la limitación de JVM ).

NoiseMaker noiseMaker = new Cat(); // Valid
FoodEater foodEater = new Cat(); // Valid
Cat cat = new Cat(); // valid

Cat invalid1 = new NoiseMaker(); // Invalid
Cat invalid2 = new FoodEater(); // Invalid

Nota:

  1. Todas las variables declaradas en una interfaz son public static final
  2. Todos los métodos declarados en una interfaz son public abstract (esta declaración es válida solo a través de Java 7. Desde Java 8, se le permite tener métodos en una interfaz, que no tienen por qué ser abstractos; estos métodos se conocen como métodos predeterminados )
  3. Las interfaces no pueden ser declaradas como final
  4. Si más de una interfaz declara un método que tiene una firma idéntica, efectivamente se trata como un solo método y no se puede distinguir de qué método de interfaz se implementa
  5. Se generará un archivo InterfaceName.class correspondiente para cada interfaz, luego de la compilación

Extendiendo una interfaz

Una interfaz puede extender otra interfaz a través de la extends palabra clave.

public interface BasicResourceService {
    Resource getResource();
}

public interface ExtendedResourceService extends BasicResourceService {
    void updateResource(Resource resource);
}

Ahora, una clase que implemente ExtendedResourceService deberá implementar tanto getResource() como updateResource() .

Extendiendo multiples interfaces

A diferencia de las clases, la extends palabra clave puede utilizarse para ampliar varias interfaces (separados por comas) permitiendo combinaciones de interfaces en una nueva interfaz

public interface BasicResourceService {
    Resource getResource();
}

public interface AlternateResourceService {
    Resource getAlternateResource();
}

public interface ExtendedResourceService extends BasicResourceService, AlternateResourceService {
    Resource updateResource(Resource resource);
}

En este caso, una clase que implemente ExtendedResourceService deberá implementar getResource() , getAlternateResource() y updateResource() .

Usando interfaces con genéricos

Supongamos que desea definir una interfaz que permita publicar / consumir datos hacia y desde diferentes tipos de canales (por ejemplo, AMQP, JMS, etc.), pero desea poder cambiar los detalles de la implementación ...

Definamos una interfaz de IO básica que puede reutilizarse en múltiples implementaciones:

public interface IO<IncomingType, OutgoingType> {

    void publish(OutgoingType data);
    IncomingType consume();
    IncomingType RPCSubmit(OutgoingType data);

}

Ahora puedo crear una instancia de esa interfaz, pero como no tenemos implementaciones predeterminadas para esos métodos, necesitará una implementación cuando la creamos:

    IO<String, String> mockIO = new IO<String, String>() {

        private String channel = "somechannel";

        @Override
        public void publish(String data) {
            System.out.println("Publishing " + data + " to " + channel);
        }

        @Override
        public String consume() {
            System.out.println("Consuming from " + channel);
            return "some useful data";
        }

        @Override
        public String RPCSubmit(String data) {
            return "received " + data + " just now ";
        }

    };

    mockIO.consume(); // prints: Consuming from somechannel
    mockIO.publish("TestData"); // Publishing TestData to somechannel
    System.out.println(mockIO.RPCSubmit("TestData")); // received TestData just now

También podemos hacer algo más útil con esa interfaz, digamos que queremos usarla para envolver algunas funciones básicas de RabbitMQ:

public class RabbitMQ implements IO<String, String> {

    private String exchange;
    private String queue;

    public RabbitMQ(String exchange, String queue){
        this.exchange = exchange;
        this.queue = queue;
    }

    @Override
    public void publish(String data) {
        rabbit.basicPublish(exchange, queue, data.getBytes());
    }

    @Override
    public String consume() {
        return rabbit.basicConsume(exchange, queue);
    }

    @Override
    public String RPCSubmit(String data) {
        return rabbit.rpcPublish(exchange, queue, data);
    }

}

Digamos que quiero usar esta interfaz de E / S ahora como una forma de contar las visitas a mi sitio web desde mi último reinicio del sistema y luego poder mostrar el número total de visitas. Puede hacer algo como esto:

import java.util.concurrent.atomic.AtomicLong;

public class VisitCounter implements IO<Long, Integer> {

    private static AtomicLong websiteCounter = new AtomicLong(0);
    
    @Override
    public void publish(Integer count) {
        websiteCounter.addAndGet(count);
    }

    @Override
    public Long consume() {
        return websiteCounter.get();
    }

    @Override
    public Long RPCSubmit(Integer count) {
        return websiteCounter.addAndGet(count);
    }
    
}

Ahora vamos a usar el VisitCounter:

    VisitCounter counter = new VisitCounter();

    // just had 4 visits, yay
    counter.publish(4);
    // just had another visit, yay
    counter.publish(1);

    // get data for stats counter
    System.out.println(counter.consume()); // prints 5

    // show data for stats counter page, but include that as a page view
    System.out.println(counter.RPCSubmit(1)); // prints 6

Al implementar varias interfaces, no puede implementar la misma interfaz dos veces. Eso también se aplica a las interfaces genéricas. Por lo tanto, el siguiente código no es válido y generará un error de compilación:

interface Printer<T> {
    void print(T value);
}

// Invalid!
class SystemPrinter implements Printer<Double>, Printer<Integer> {
    @Override public void print(Double d){ System.out.println("Decimal: " + d); }
    @Override public void print(Integer i){ System.out.println("Discrete: " + i); }
}

Utilidad de las interfaces.

Las interfaces pueden ser extremadamente útiles en muchos casos. Por ejemplo, supongamos que tenía una lista de animales y quería recorrer la lista, cada uno imprimiendo el sonido que hacen.

{cat, dog, bird}

Una forma de hacer esto sería usar interfaces. Esto permitiría que se llame al mismo método en todas las clases

public interface Animal {
    public String getSound();
}

Cualquier clase que implements Animal también debe tener un método getSound() , pero todas pueden tener implementaciones diferentes

public class Dog implements Animal {
    public String getSound() {
        return "Woof";
    }
}

public class Cat implements Animal {
    public String getSound() {
        return "Meow";
    }
}

public class Bird implements Animal{
    public String getSound() {
        return "Chirp";
    }
}

Ahora tenemos tres clases diferentes, cada una de las cuales tiene un método getSound() . Debido a que todas estas clases implement la interfaz Animal , que declara el método getSound() , cualquier instancia de un Animal puede tener getSound() llamada.

Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();

dog.getSound(); // "Woof"
cat.getSound(); // "Meow"
bird.getSound(); // "Chirp"

Debido a que cada uno de estos es un Animal , incluso podríamos poner a los animales en una lista, recorrerlos e imprimir sus sonidos.

Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal animal : animals) {
    System.out.println(animal.getSound());
}

Debido a que el orden de la matriz es Dog , Cat y luego Bird , "Woof Meow Chirp" se imprimirá en la consola.

Las interfaces también se pueden utilizar como valor de retorno para las funciones. Por ejemplo, devolver un Dog si la entrada es "perro" , Cat si la entrada es "gato" y Bird si es "pájaro" , y luego imprimir el sonido de ese animal se podría hacer usando

public Animal getAnimalByName(String name) {
    switch(name.toLowerCase()) {
        case "dog":
            return new Dog();
        case "cat":
            return new Cat();
        case "bird":
            return new Bird();
        default:
            return null;
    }
}

public String getAnimalSoundByName(String name){
    Animal animal = getAnimalByName(name);
    if (animal == null) {
        return null;
    } else { 
        return animal.getSound();
    }
}

String dogSound = getAnimalSoundByName("dog"); // "Woof"
String catSound = getAnimalSoundByName("cat"); // "Meow"
String birdSound = getAnimalSoundByName("bird"); // "Chirp"
String lightbulbSound = getAnimalSoundByName("lightbulb"); // null

Las interfaces también son útiles para la extensibilidad, porque si quieres agregar un nuevo tipo de Animal , no necesitarás cambiar nada con las operaciones que realices en ellos.

Implementando interfaces en una clase abstracta.

Un método definido en una interface es por defecto public abstract . Cuando una abstract class implementa una interface , los métodos que se definen en la interface no tienen que ser implementado por el abstract class . Esto se debe a que una class que se declara abstract puede contener declaraciones de métodos abstractos. Por lo tanto, es responsabilidad de la primera subclase concreta implementar cualquier método abstract heredado de cualquier interfaz y / o la abstract class .

public interface NoiseMaker {
    void makeNoise();
}

public abstract class Animal implements NoiseMaker {
    //Does not need to declare or implement makeNoise()
    public abstract void eat();
}

//Because Dog is concrete, it must define both makeNoise() and eat()
public class Dog extends Animal {
    @Override
    public void makeNoise() {
        System.out.println("Borf borf");
    }

    @Override
    public void eat() {
        System.out.println("Dog eats some kibble.");
    }
}

Desde Java 8 en adelante, es posible que una interface declare implementaciones default de métodos, lo que significa que el método no será abstract , por lo tanto, no se obligará a ninguna subclase concreta a implementar el método, sino que heredará la implementación default menos que se anule.

Métodos predeterminados

Introducido en Java 8, los métodos predeterminados son una forma de especificar una implementación dentro de una interfaz. Esto podría usarse para evitar la típica clase "Base" o "Resumen" al proporcionar una implementación parcial de una interfaz y restringir la jerarquía de subclases.

Implementación del patrón observador

Por ejemplo, es posible implementar el patrón Observer-Listener directamente en la interfaz, proporcionando más flexibilidad a las clases de implementación.

interface Observer {
    void onAction(String a);
}

interface Observable{
    public abstract List<Observer> getObservers();

    public default void addObserver(Observer o){
        getObservers().add(o);
    }

    public default void notify(String something ){
        for( Observer l : getObservers() ){
            l.onAction(something);
        }
    }
}

Ahora, cualquier clase puede convertirse en "Observable" solo con la implementación de la interfaz de Observable, y ser libre de ser parte de una jerarquía de clases diferente.

abstract class Worker{
    public abstract void work();
}

public class MyWorker extends Worker implements Observable {

    private List<Observer> myObservers = new ArrayList<Observer>();
    
    @Override
    public List<Observer> getObservers() {
        return myObservers;
    }

    @Override
    public void work(){
        notify("Started work");

        // Code goes here...

        notify("Completed work");
    }
    
    public static void main(String[] args) {    
        MyWorker w = new MyWorker();
       
        w.addListener(new Observer() {
            @Override
            public void onAction(String a) {
                System.out.println(a + " (" + new Date() + ")");
            }
        });
        
        w.work();
    }
}

Problema del diamante

El compilador en Java 8 es consciente del problema de diamante que se produce cuando una clase implementa interfaces que contienen un método con la misma firma.

Para resolverlo, una clase de implementación debe anular el método compartido y proporcionar su propia implementación.

interface InterfaceA {
    public default String getName(){
        return "a";
    }
}

interface InterfaceB {
    public default String getName(){
        return "b";
    }
}

public class ImpClass implements InterfaceA, InterfaceB {

    @Override
    public String getName() {    
        //Must provide its own implementation
        return InterfaceA.super.getName() + InterfaceB.super.getName();
    }
    
    public static void main(String[] args) {    
        ImpClass c = new ImpClass();
        
        System.out.println( c.getName() );                   // Prints "ab"
        System.out.println( ((InterfaceA)c).getName() );     // Prints "ab"
        System.out.println( ((InterfaceB)c).getName() );     // Prints "ab"
    }
}

Todavía existe el problema de tener métodos con el mismo nombre y parámetros con diferentes tipos de retorno, que no se compilarán.

Utilice métodos predeterminados para resolver problemas de compatibilidad

Las implementaciones de los métodos predeterminados son muy útiles si se agrega un método a una interfaz en un sistema existente en el que varias clases usan las interfaces.

Para evitar dividir todo el sistema, puede proporcionar una implementación de método predeterminada cuando agrega un método a una interfaz. De esta manera, el sistema todavía se compilará y las implementaciones reales se pueden hacer paso a paso.


Para obtener más información, consulte el tema Métodos predeterminados .

Modificadores en interfaces

La Guía de estilo de Java de Oracle dice:

Los modificadores no deben escribirse cuando están implícitos.

(Ver Modificadores en Oracle Official Code Standard para el contexto y un enlace al documento de Oracle real).

Esta guía de estilo se aplica particularmente a las interfaces. Consideremos el siguiente fragmento de código:

interface I {
    public static final int VARIABLE = 0;

    public abstract void method();

    public static void staticMethod() { ... }
    public default void defaultMethod() { ... }
}

Variables

Todas las variables de interfaz son constantes implícitamente con modificadores public (accesibles para todos), static (accesibles por nombre de interfaz) y final (deben inicializarse durante la declaración):

public static final int VARIABLE = 0;

Métodos

  1. Todos los métodos que no proporcionan implementación son implícitamente public y abstract .
public abstract void method();
Java SE 8
  1. Todos los métodos con modificador static o default deben proporcionar implementación y son implícitamente public .
public static void staticMethod() { ... }

Después de que se hayan aplicado todos los cambios anteriores, obtendremos lo siguiente:

interface I {
    int VARIABLE = 0;
    
    void method();

    static void staticMethod() { ... }
    default void defaultMethod() { ... }
}

Reforzar los parámetros de tipo acotado.

Los parámetros de tipo limitado le permiten establecer restricciones en los argumentos de tipo genérico:

class SomeClass {

}

class Demo<T extends SomeClass> {

}

Pero un parámetro de tipo solo puede vincularse a un solo tipo de clase.

Un tipo de interfaz se puede vincular a un tipo que ya tenía un enlace. Esto se logra usando el símbolo & :

interface SomeInterface {

}

class GenericClass<T extends SomeClass & SomeInterface> {

}

Esto fortalece el enlace, lo que potencialmente requiere que los argumentos de tipo se deriven de múltiples tipos.

Se pueden enlazar varios tipos de interfaz a un parámetro de tipo:

class Demo<T extends SomeClass & FirstInterface & SecondInterface> {

}

Pero debe utilizarse con precaución. Los enlaces de interfaz múltiples suelen ser un signo de olor de código , lo que sugiere que se debe crear un nuevo tipo que actúe como un adaptador para los otros tipos:

interface NewInterface extends FirstInterface, SecondInterface {

}

class Demo<T extends SomeClass & NewInterface> {

}


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow