Java Language
Schnittstellen
Suche…
Einführung
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:
- Alle in einer Schnittstelle deklarierten Variablen sind
public static final
- 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. ) - Schnittstellen können nicht als
final
deklariert werden - 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
- 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
- Alle Methoden, die keine Implementierung bieten, sind implizit
public
undabstract
.
public abstract void method();
- Alle Methoden mit
static
oderdefault
müssen implementiert werden und sind implizitpublic
.
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> {
}