Ricerca…


introduzione

Un'interfaccia è un tipo di riferimento, simile a una classe, che può essere dichiarata tramite interface parola chiave. Le interfacce possono contenere solo costanti, firme dei metodi, metodi predefiniti, metodi statici e tipi nidificati. I corpi dei metodi esistono solo per metodi predefiniti e metodi statici. Come le classi astratte, le interfacce non possono essere istanziate: possono essere implementate solo da classi o estese da altre interfacce. L'interfaccia è un modo comune per ottenere la completa astrazione in Java.

Sintassi

  • interfaccia pubblica Foo {void foo (); / * altri metodi * /}
  • l'interfaccia pubblica Foo1 estende Foo {void bar (); / * altri metodi * /}
  • classe pubblica Foo2 implementa Foo, Foo1 {/ * implementazione di Foo e Foo1 * /}

Dichiarazione e implementazione di un'interfaccia

Dichiarazione di un'interfaccia che utilizza la parola chiave interface :

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

Sostituisci annotazione

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

Questo costringe il compilatore a controllare che stiamo eseguendo l'override e impedisce al programma di definire un nuovo metodo o rovinare la firma del metodo.

Le interfacce sono implementate usando la parola chiave implements .

public class Cat implements Animal {

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

public class Dog implements Animal {

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

Nell'esempio, le classi Cat e Dog devono definire il metodo getSound() poiché i metodi di un'interfaccia sono intrinsecamente astratti (ad eccezione dei metodi predefiniti).

Utilizzando le interfacce

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

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

Implementazione di più interfacce

Una classe Java può implementare più interfacce.

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

Si noti come la classe Cat deve implementare i metodi abstract ereditati in entrambe le interfacce. Inoltre, nota come una classe può praticamente implementare tutte le interfacce necessarie (c'è un limite di 65.535 a causa della Limitazione 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. Tutte le variabili dichiarate in un'interfaccia sono public static final
  2. Tutti i metodi dichiarati in un'interfaccia metodi sono public abstract (Questa dichiarazione è valida solo tramite Java 7. Da Java 8, è possibile avere metodi in un'interfaccia, che non devono essere astratti, tali metodi sono noti come metodi predefiniti )
  3. Le interfacce non possono essere dichiarate final
  4. Se più di una interfaccia dichiara un metodo che ha la stessa firma, allora efficacemente viene trattato come un solo metodo e non è possibile distinguere da quale metodo di interfaccia è implementato
  5. Un file InterfaceName.class corrispondente verrà generato per ogni interfaccia, dopo la compilazione

Estendere un'interfaccia

Un'interfaccia può estendere un'altra interfaccia tramite la parola chiave extends .

public interface BasicResourceService {
    Resource getResource();
}

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

Ora una classe che implementa ExtendedResourceService dovrà implementare sia getResource() che updateResource() .

Estensione di più interfacce

A differenza delle classi, la parola chiave extends può essere utilizzata per estendere più interfacce (separate da virgole) consentendo combinazioni di interfacce in una nuova interfaccia

public interface BasicResourceService {
    Resource getResource();
}

public interface AlternateResourceService {
    Resource getAlternateResource();
}

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

In questo caso, una classe che implementa ExtendedResourceService dovrà implementare getResource() , getAlternateResource() e updateResource() .

Utilizzo delle interfacce con Generics

Supponiamo che tu voglia definire un'interfaccia che consenta la pubblicazione / il consumo di dati da e verso diversi tipi di canali (ad es. AMQP, JMS, ecc.), Ma tu vuoi essere in grado di cambiare i dettagli di implementazione ...

Definiamo un'interfaccia IO di base che può essere riutilizzata in più implementazioni:

public interface IO<IncomingType, OutgoingType> {

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

}

Ora posso creare un'istanza di questa interfaccia, ma dal momento che non abbiamo implementazioni predefinite per questi metodi, avrà bisogno di un'implementazione quando la istanziamo:

    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

Possiamo anche fare qualcosa di più utile con quell'interfaccia, diciamo che vogliamo usarlo per avvolgere alcune funzioni base di 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);
    }

}

Diciamo che voglio usare questa interfaccia IO ora come un modo per contare le visite al mio sito web dal mio ultimo riavvio del sistema e quindi essere in grado di visualizzare il numero totale di visite - puoi fare qualcosa del genere:

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

Ora usiamo il 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

Quando si implementano più interfacce, non è possibile implementare la stessa interfaccia due volte. Questo vale anche per le interfacce generiche. Pertanto, il seguente codice non è valido e genererà un errore di compilazione:

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

Utilità delle interfacce

Le interfacce possono essere estremamente utili in molti casi. Ad esempio, supponiamo di avere una lista di animali e di voler scorrere l'elenco, ognuno dei quali stampa il suono che produce.

{cat, dog, bird}

Un modo per farlo sarebbe utilizzare le interfacce. Ciò consentirebbe lo stesso metodo per essere chiamato su tutte le classi

public interface Animal {
    public String getSound();
}

Qualsiasi classe che implements Animal deve avere anche un metodo getSound() , tuttavia possono avere implementazioni diverse

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";
    }
}

Ora abbiamo tre diverse classi, ognuna delle quali ha un metodo getSound() . Poiché tutte queste classi implement l'interfaccia Animal , che dichiara il metodo getSound() , qualsiasi istanza di un Animal può avere getSound() chiamato su di esso

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

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

Poiché ognuno di questi è un Animal , potremmo persino inserire gli animali in un elenco, scorrerli tra loro e stampare i loro suoni

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

Poiché l'ordine dell'array è Dog , Cat e quindi Bird , "Woof Meow Chirp" verrà stampato sulla console.

Le interfacce possono anche essere utilizzate come valore di ritorno per le funzioni. Ad esempio, restituendo un Dog se l'input è "dog" , Cat se l'input è "cat" e Bird se è "bird" , e quindi si può stampare il suono di quell'animale 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

Le interfacce sono anche utili per l'estensibilità, perché se si desidera aggiungere un nuovo tipo di Animal , non è necessario modificare nulla con le operazioni eseguite su di esse.

Implementazione di interfacce in una classe astratta

Un metodo definito in interface è di default public abstract . Quando una abstract class implementa interface , i metodi definiti interface non devono essere implementati dalla abstract class . Questo perché una class dichiarata abstract può contenere dichiarazioni di metodi astratti. È quindi responsabilità della prima sottoclasse concreta implementare qualsiasi metodo abstract ereditato da qualsiasi interfaccia e / o 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.");
    }
}

Da Java 8 in poi è possibile che interface dichiari implementazioni default di metodi, il che significa che il metodo non sarà abstract , quindi qualsiasi sottoclasse concreta non sarà forzata ad implementare il metodo ma erediterà l'implementazione default meno che non venga sovrascritta.

Metodi predefiniti

Introdotto in Java 8, i metodi predefiniti sono un modo per specificare un'implementazione all'interno di un'interfaccia. Questo potrebbe essere usato per evitare la tipica classe "Base" o "Abstract" fornendo un'implementazione parziale di un'interfaccia e limitando la gerarchia delle sottoclassi.

Implementazione del modello di osservatore

Ad esempio, è possibile implementare il pattern Observer-Listener direttamente nell'interfaccia, fornendo maggiore flessibilità alle classi di implementazione.

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

Ora, qualsiasi classe può essere resa "Osservabile" semplicemente implementando l'interfaccia Observable, pur essendo libera di far parte di una diversa gerarchia di classi.

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

Il compilatore in Java 8 è a conoscenza del problema dei rombi causato da una classe che implementa interfacce contenenti un metodo con la stessa firma.

Per risolverlo, una classe di implementazione deve sovrascrivere il metodo condiviso e fornire la propria implementazione.

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"
    }
}

C'è ancora il problema di avere metodi con lo stesso nome e parametri con diversi tipi di ritorno, che non verranno compilati.

Utilizzare i metodi predefiniti per risolvere i problemi di compatibilità

Le implementazioni del metodo predefinito sono molto utili se un metodo viene aggiunto a un'interfaccia in un sistema esistente in cui le interfacce vengono utilizzate da più classi.

Per evitare di suddividere l'intero sistema, è possibile fornire un'implementazione di metodo predefinita quando si aggiunge un metodo a un'interfaccia. In questo modo, il sistema sarà ancora compilato e le implementazioni effettive possono essere fatte passo dopo passo.


Per ulteriori informazioni, vedere l'argomento Metodi predefiniti .

Modificatori nelle interfacce

L'Oracle Java Style Guide afferma:

I modificatori non dovrebbero essere scritti quando sono impliciti.

(Vedi Modificatori in Oracle Official Code Standard per il contesto e un link al documento Oracle effettivo.)

Questo orientamento di stile si applica in particolare alle interfacce. Consideriamo il seguente frammento di codice:

interface I {
    public static final int VARIABLE = 0;

    public abstract void method();

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

variabili

Tutte le variabili di interfaccia sono costanti implicite con modificatori public impliciti (accessibili a tutti), static (accessibili tramite nome interfaccia) e final (devono essere inizializzati durante la dichiarazione):

public static final int VARIABLE = 0;

metodi

  1. Tutti i metodi che non prevedono l'implementazione sono implicitamente public e abstract .
public abstract void method();
Java SE 8
  1. Tutti i metodi con modificatore static o default devono fornire l'implementazione e sono implicitamente public .
public static void staticMethod() { ... }

Dopo aver applicato tutte le modifiche precedenti, otterremo quanto segue:

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

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

Rafforza i parametri del tipo limitato

I parametri di tipo limitato consentono di impostare restrizioni sugli argomenti di tipo generico:

class SomeClass {

}

class Demo<T extends SomeClass> {

}

Ma un parametro di tipo può collegarsi solo a un singolo tipo di classe.

Un tipo di interfaccia può essere associato a un tipo che ha già un legame. Questo si ottiene usando il simbolo & :

interface SomeInterface {

}

class GenericClass<T extends SomeClass & SomeInterface> {

}

Ciò rafforza il legame, potenzialmente richiedendo argomenti di tipo per derivare da più tipi.

Più tipi di interfaccia possono essere associati a un parametro di tipo:

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

}

Ma dovrebbe essere usato con cautela. I collegamenti di interfacce multiple sono di solito un segno di odore di codice , suggerendo che dovrebbe essere creato un nuovo tipo che funge da adattatore per gli altri tipi:

interface NewInterface extends FirstInterface, SecondInterface {

}

class Demo<T extends SomeClass & NewInterface> {

}


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow