Suche…


Einführung

Eine Schnittstelle ist ein Referenztyp, der einer Klasse ähnelt und mit dem interface deklariert werden kann. Schnittstellen können nur Konstanten, Methodensignaturen, Standardmethoden, statische Methoden und verschachtelte Typen enthalten. Methodentexte sind nur für Standardmethoden und statische Methoden vorhanden. Wie abstrakte Klassen können Schnittstellen nicht instanziiert werden. Sie können nur von Klassen implementiert oder von anderen Schnittstellen erweitert werden. Die Schnittstelle ist eine gängige Methode, um eine vollständige Abstraktion in Java zu erreichen.

Syntax

  • öffentliche Schnittstelle Foo {void foo (); / * andere Methoden * /}
  • öffentliche Schnittstelle Foo1 erweitert Foo {Leerverbinder (); / * andere Methoden * /}
  • öffentliche Klasse Foo2 implementiert Foo, Foo1 {/ * Implementierung von Foo und Foo1 * /}

Eine Schnittstelle deklarieren und implementieren

Deklaration einer Schnittstelle mit dem interface :

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

Annotation überschreiben

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

Dies zwingt den Compiler, zu prüfen, ob wir überschreiben, und verhindert, dass das Programm eine neue Methode definiert oder die Methodensignatur durcheinander bringt.

Schnittstellen werden mit dem Schlüsselwort implements implements .

public class Cat implements Animal {

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

public class Dog implements Animal {

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

Im Beispiel Klassen Cat und Dog müssen definieren getSound() Methode als Methoden einer Schnittstelle von Natur aus abstrakt sind (mit Ausnahme von Standardmethoden).

Verwenden der Schnittstellen

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

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

Implementierung mehrerer Schnittstellen

Eine Java-Klasse kann mehrere Schnittstellen implementieren.

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

Beachten Sie, wie die Cat Klasse die geerbten abstract Methoden in beiden Schnittstellen implementieren muss . Beachten Sie außerdem, wie eine Klasse praktisch beliebig viele Schnittstellen implementieren kann (aufgrund der JVM-Einschränkung gibt es eine Grenze von 65.535 ).

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

Hinweis:

  1. Alle in einer Schnittstelle deklarierten Variablen sind public static final
  2. Alle in einer Schnittstellenmethode deklarierten Methoden sind public abstract (Diese Anweisung ist nur über Java 7 gültig. In Java 8 dürfen Sie Methoden in einer Schnittstelle haben, die nicht abstrakt sein müssen; solche Methoden werden als Standardmethoden bezeichnet. )
  3. Schnittstellen können nicht als final deklariert werden
  4. Wenn mehr als eine Schnittstelle eine Methode mit identischer Signatur deklariert, wird sie effektiv als nur eine Methode behandelt, und Sie können nicht unterscheiden, welche Schnittstellenmethode implementiert ist
  5. Bei der Kompilierung wird für jede Schnittstelle eine entsprechende InterfaceName.class- Datei generiert

Eine Schnittstelle erweitern

Eine Schnittstelle kann eine andere Schnittstelle über das Schlüsselwort extend extends .

public interface BasicResourceService {
    Resource getResource();
}

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

Jetzt muss eine Klasse, die ExtendedResourceService implementiert, sowohl getResource() als auch updateResource() implementieren.

Mehrere Schnittstellen erweitern

Im Gegensatz zu Klassen, die extends Schlüsselwort kann verwendet werden , um mehrere Schnittstellen zu erweitern (durch Kommas getrennt) so dass für Kombinationen von Schnittstellen in eine neue Schnittstelle

public interface BasicResourceService {
    Resource getResource();
}

public interface AlternateResourceService {
    Resource getAlternateResource();
}

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

In diesem Fall muss eine Klasse, die ExtendedResourceService implementiert, getResource() , getAlternateResource() und updateResource() .

Verwenden von Schnittstellen mit Generics

Angenommen, Sie möchten eine Schnittstelle definieren, die das Veröffentlichen / Konsumieren von Daten in und aus verschiedenen Kanaltypen (z. B. AMQP, JMS usw.) ermöglicht. Sie möchten jedoch die Implementierungsdetails ausschalten können.

Definieren wir eine grundlegende E / A-Schnittstelle, die in mehreren Implementierungen wiederverwendet werden kann:

public interface IO<IncomingType, OutgoingType> {

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

}

Jetzt kann ich diese Schnittstelle instanziieren, aber da es für diese Methoden keine Standardimplementierungen gibt, ist eine Implementierung erforderlich, wenn wir sie instanziieren:

    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

Wir können auch etwas nützlicheres mit dieser Schnittstelle machen, nehmen wir an, wir wollen es verwenden, um einige grundlegende RabbitMQ-Funktionen einzubinden:

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

}

Angenommen, ich möchte diese E / A-Schnittstelle jetzt verwenden, um Besuche auf meiner Website seit dem letzten Neustart des Systems zu zählen und dann die Gesamtzahl der Besuche anzeigen zu können. Sie können beispielsweise Folgendes tun:

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

Jetzt nutzen wir den 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

Wenn Sie mehrere Schnittstellen implementieren, können Sie dieselbe Schnittstelle nicht zweimal implementieren. Das gilt auch für generische Schnittstellen. Daher ist der folgende Code ungültig und führt zu einem Kompilierungsfehler:

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

Nützlichkeit von Schnittstellen

Schnittstellen können in vielen Fällen äußerst hilfreich sein. Angenommen, Sie hatten eine Liste von Tieren und wollten die Liste durchlaufen, wobei jeweils der Ton gedruckt wurde.

{cat, dog, bird}

Eine Möglichkeit, dies zu tun, wäre die Verwendung von Schnittstellen. Dies würde ermöglichen, dass dieselbe Methode für alle Klassen aufgerufen wird

public interface Animal {
    public String getSound();
}

Jede Klasse, implements Animal muss auch eine getSound() -Methode enthalten. Sie kann jedoch unterschiedliche Implementierungen haben

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

Wir haben jetzt drei verschiedene Klassen, von denen jede eine getSound() -Methode hat. Da alle diese Klassen implement die Animal - Schnittstelle, die das erklärt getSound() Methode, jede Instanz eines Animal haben kann getSound() auf sie genannt

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

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

Da jedes Animal ein Animal , könnten wir die Tiere sogar in eine Liste setzen, sie durchlaufen und ihre Geräusche ausdrucken

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

Da die Reihenfolge des Arrays Dog , Cat und Bird lautet, wird "Woof Meow Chirp" auf die Konsole gedruckt.

Schnittstellen können auch als Rückgabewert für Funktionen verwendet werden. Zum Beispiel die Rückgabe eines Dog wenn die Eingabe "Hund" ist , Cat wenn die Eingabe "Katze" ist , und Bird wenn es "Vogel" ist , und dann das Geräusch dieses Tieres gedruckt werden könnte

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

Schnittstellen sind auch für die Erweiterbarkeit hilfreich. Wenn Sie einen neuen Animal Typ hinzufügen möchten, müssen Sie nichts an den Operationen ändern, die Sie für sie ausführen.

Schnittstellen in einer abstrakten Klasse implementieren

Eine in einer interface definierte Methode ist standardmäßig public abstract . Wenn eine abstract class eine interface implementiert, müssen alle Methoden, die in der interface definiert sind, nicht von der abstract class implementiert werden. Dies liegt daran, dass eine class , die als abstract deklariert ist, abstrakte Methodendeklarationen enthalten kann. Es ist daher die Aufgabe der ersten konkreten Unterklasse, irgendwelche abstract Methoden zu implementieren, die von beliebigen Schnittstellen und / oder der abstract class geerbt werden.

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

Ab Java 8 ist es möglich, dass eine interface default von Methoden deklariert. default bedeutet, dass die Methode nicht abstract ist. Konkrete Unterklassen müssen die Methode nicht implementieren, erben jedoch die default , sofern sie nicht überschrieben werden.

Standardmethoden

In Java 8 eingeführt, bieten Standardmethoden eine Möglichkeit, eine Implementierung innerhalb einer Schnittstelle anzugeben. Dies könnte verwendet werden, um die typische Klasse "Base" oder "Abstract" zu vermeiden, indem eine teilweise Implementierung einer Schnittstelle bereitgestellt wird und die Hierarchie der Unterklassen eingeschränkt wird.

Beobachtermuster-Implementierung

Beispielsweise ist es möglich, das Observer-Listener-Muster direkt in die Benutzeroberfläche zu implementieren, wodurch die implementierenden Klassen flexibler werden.

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

Jetzt kann jede Klasse durch das Implementieren der Observable-Schnittstelle "beobachtbar" gemacht werden, während sie frei ist, Teil einer anderen Klassenhierarchie zu sein.

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

Diamantproblem

Dem Compiler in Java 8 ist das Diamantproblem bekannt, das verursacht wird, wenn eine Klasse Schnittstellen implementiert, die eine Methode mit derselben Signatur enthalten.

Um dies zu lösen, muss eine implementierende Klasse die gemeinsam genutzte Methode überschreiben und ihre eigene Implementierung bereitstellen.

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

Es gibt immer noch das Problem, Methoden mit demselben Namen und Parametern mit unterschiedlichen Rückgabetypen zu haben, die nicht kompiliert werden können.

Verwenden Sie Standardmethoden, um Kompatibilitätsprobleme zu beheben

Die Standardmethodenimplementierungen sind sehr praktisch, wenn einer Schnittstelle in einem vorhandenen System eine Methode hinzugefügt wird, in der die Schnittstellen von mehreren Klassen verwendet werden.

Um ein Aufbrechen des gesamten Systems zu vermeiden, können Sie eine Standardmethodenimplementierung bereitstellen, wenn Sie einer Schnittstelle eine Methode hinzufügen. Auf diese Weise wird das System immer noch kompiliert und die eigentlichen Implementierungen können Schritt für Schritt durchgeführt werden.


Weitere Informationen finden Sie im Thema Standardmethoden .

Modifikatoren in Schnittstellen

Im Oracle Java Style Guide heißt es:

Modifikatoren sollten nicht ausgeschrieben werden, wenn sie implizit sind.

(Siehe Modifikatoren in Oracle Official Code Standard für den Kontext und einen Link zum eigentlichen Oracle-Dokument.)

Diese Stilanleitung gilt insbesondere für Schnittstellen. Betrachten wir das folgende Code-Snippet:

interface I {
    public static final int VARIABLE = 0;

    public abstract void method();

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

Variablen

Alle Schnittstellenvariablen sind implizit Konstanten mit impliziten public (zugänglich für alle), static (sind über Schnittstellennamen zugänglich) und final (müssen während der Deklaration initialisiert werden):

public static final int VARIABLE = 0;

Methoden

  1. Alle Methoden, die keine Implementierung bieten, sind implizit public und abstract .
public abstract void method();
Java SE 8
  1. Alle Methoden mit static oder default müssen implementiert werden und sind implizit public .
public static void staticMethod() { ... }

Nachdem alle oben genannten Änderungen angewendet wurden, erhalten Sie Folgendes:

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

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

Verstärken Sie die Parameter des beschränkten Typs

Begrenzte Typparameter ermöglichen das Festlegen von Einschränkungen für generische Typargumente:

class SomeClass {

}

class Demo<T extends SomeClass> {

}

Ein Typparameter kann jedoch nur an einen einzelnen Klassentyp gebunden werden.

Ein Schnittstellentyp kann an einen Typ gebunden sein, der bereits eine Bindung hatte. Dies wird mit dem Symbol & :

interface SomeInterface {

}

class GenericClass<T extends SomeClass & SomeInterface> {

}

Dies stärkt die Bindung und erfordert möglicherweise Argumente, die von mehreren Typen abgeleitet werden.

Mehrere Schnittstellentypen können an einen Typparameter gebunden werden:

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

}

Sollte aber mit Vorsicht angewendet werden. Mehrere Schnittstellenbindungen sind in der Regel ein Zeichen für einen Codegeruch , was darauf hindeutet, dass ein neuer Typ erstellt werden sollte, der als Adapter für die anderen Typen fungiert:

interface NewInterface extends FirstInterface, SecondInterface {

}

class Demo<T extends SomeClass & NewInterface> {

}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow