Hallo! Heute werden wir über ein wichtiges Konzept in Java sprechen: Schnittstellen. Das Wort kommt Ihnen wahrscheinlich bekannt vor. Beispielsweise verfügen die meisten Computerprogramme und Spiele über Schnittstellen. Im weitesten Sinne ist eine Schnittstelle eine Art „Fernsteuerung“, die zwei interagierende Parteien verbindet. Ein einfaches Beispiel für eine Schnittstelle im Alltag ist eine TV-Fernbedienung. Es verbindet zwei Objekte – eine Person und einen Fernseher – und führt verschiedene Aufgaben aus: Erhöhen oder verringern Sie die Lautstärke, wechseln Sie die Kanäle und schalten Sie den Fernseher ein oder aus. Eine Partei (die Person) muss auf die Schnittstelle zugreifen (eine Taste auf der Fernbedienung drücken), um die zweite Partei zur Ausführung der Aktion zu veranlassen. Zum Beispiel, um den Fernseher zum nächsten Sender wechseln zu lassen. Darüber hinaus muss der Benutzer Sie müssen nicht wissen, wie der Fernseher organisiert ist oder wie der Kanalwechselprozess intern implementiert wird. Das Einzige, worauf der Benutzer Zugriff hat, ist die Schnittstelle. Das Hauptziel besteht darin, das gewünschte Ergebnis zu erzielen. Was hat das mit Programmierung und Java zu tun? Alles :) Das Erstellen einer Schnittstelle ist dem Erstellen einer regulären Klasse sehr ähnlich, verwendet jedoch stattdessen das WortKlasse geben wir das Wort Schnittstelle an . Schauen wir uns die einfachste Java-Schnittstelle an, sehen, wie sie funktioniert und warum wir sie brauchen würden:
public interface CanSwim {
public void swim();
}
Wir haben eine CanSwim- Schnittstelle erstellt. Es ist ein bisschen wie unsere Fernbedienung, aber mit einer „Taste“: der swim()- Methode. Aber wie verwenden wir diese Fernbedienung? Dazu müssen wir eine Methode implementieren, nämlich unseren Fernbedienungsknopf. Um eine Schnittstelle nutzen zu können, müssen einige Klassen in unserem Programm deren Methoden implementieren. Lassen Sie uns eine Klasse erfinden, deren Objekte „schwimmen“ können. Eine Duck- Klasse passt beispielsweise zu :
public class Duck implements CanSwim {
public void swim() {
System.out.println("Duck, swim!");
}
public static void main(String[] args) {
Duck duck = new Duck();
duck.swim();
}
}
„Was sehen wir hier? Die Duck- Klasse ist durch das Schlüsselwort „ implements “ mit der CanSwim- Schnittstelle „verknüpft“. Sie erinnern sich vielleicht, dass wir einen ähnlichen Mechanismus verwendet haben, um zwei Klassen durch Vererbung zu verknüpfen, aber in diesem Fall haben wir das Wort „extends“ verwendet. Für Um der Klarheit willen können wir „ öffentliche Klasse Duck implementiert CanSwim “ wörtlich übersetzen als: „Die öffentliche Duck- Klasse implementiert die CanSwim- Schnittstelle“. Das bedeutet, dass eine mit einer Schnittstelle verknüpfte Klasse alle ihre Methoden implementieren muss. Hinweis: Unsere Klasse, genau wie Die Schnittstelle verfügt über eine Methode und enthält eine gewisse Logik. Dies ist eine zwingende Voraussetzung. Wenn wir nur schreibenDuck
CanSwim
swim()
public class Duck implements CanSwim
Ohne eine swim()
Methode in der Duck
Klasse zu erstellen, gibt uns der Compiler eine Fehlermeldung: Duck ist nicht abstrakt und überschreibt die abstrakte Methode swim() in CanSwim nicht. Warum? Warum passiert das? Wenn wir den Fehler anhand des TV-Beispiels erklären, wäre das so, als würde man jemandem eine TV-Fernbedienung mit einer „Kanalwechsel“-Taste geben, mit der man die Kanäle nicht wechseln kann. Sie könnten den Knopf so oft drücken, wie Sie möchten, aber es wird nicht funktionieren. Die Fernbedienung wechselt die Kanäle nicht selbst: Sie sendet lediglich ein Signal an den Fernseher, der den komplexen Prozess des Kanalwechsels durchführt. Und so ist es auch bei unserer Ente: Sie muss schwimmen können, damit sie über die CanSwim
Schnittstelle aufgerufen werden kann. Wenn es nicht weiß wie, dannCanSwim
Die Schnittstelle verbindet nicht die beiden Parteien – die Person und das Programm. Die Person kann die swim()
Methode nicht verwenden, um Duck
innerhalb des Programms zu schwimmen. Jetzt verstehen Sie besser, wozu Schnittstellen dienen. Eine Schnittstelle beschreibt das Verhalten, das Klassen haben müssen, die die Schnittstelle implementieren. „Verhalten“ ist eine Sammlung von Methoden. Wenn wir mehrere Messenger erstellen möchten, ist es am einfachsten, eine Schnittstelle zu erstellen Messenger
. Was braucht jeder Messenger? Grundsätzlich müssen sie in der Lage sein, Nachrichten zu empfangen und zu senden.
public interface Messenger{
public void sendMessage();
public void getMessage();
}
Jetzt können wir einfach unsere Messenger-Klassen erstellen, die die entsprechende Schnittstelle implementieren. Der Compiler selbst „zwingt“ uns, sie in unseren Klassen zu implementieren. Telegramm:
public class Telegram implements Messenger {
public void sendMessage() {
System.out.println("Sending a Telegram message!");
}
public void getMessage() {
System.out.println("Receiving a Telegram message!");
}
}
WhatsApp:
public class WhatsApp implements Messenger {
public void sendMessage() {
System.out.println("Sending a WhatsApp message!");
}
public void getMessage() {
System.out.println("Reading a WhatsApp message!");
}
}
Viber:
public class Viber implements Messenger {
public void sendMessage() {
System.out.println("Sending a Viber message!");
}
public void getMessage() {
System.out.println("Receiving a Viber message!");
}
}
Welche Vorteile bietet das? Die wichtigste davon ist die lose Kopplung. Stellen Sie sich vor, wir entwerfen ein Programm, das Kundendaten sammelt. Die Client
Klasse benötigt auf jeden Fall ein Feld, das angibt, welchen spezifischen Messenger der Client verwendet. Ohne Schnittstellen würde das seltsam aussehen:
public class Client {
private WhatsApp whatsApp;
private Telegram telegram;
private Viber viber;
}
Wir haben drei Felder erstellt, aber ein Kunde kann nur einen Messenger haben. Wir wissen nur nicht, welches. Daher müssen wir der Klasse alle Möglichkeiten hinzufügen, um mit dem Kunden kommunizieren zu können. Es stellt sich heraus, dass ein oder zwei davon immer null
vom Programm überhaupt nicht benötigt werden. Nutzen Sie stattdessen lieber unsere Schnittstelle:
public class Client {
private Messenger messenger;
}
Dies ist ein Beispiel für eine lose Kopplung! Anstatt in der Client
Klasse eine bestimmte Messenger-Klasse anzugeben, geben wir lediglich an, dass der Client über einen Messenger verfügt. Welche genau das ist, wird während des Programmablaufs ermittelt. Aber warum brauchen wir dafür Schnittstellen? Warum wurden sie überhaupt zur Sprache hinzugefügt? Das ist eine gute Frage – und die richtige Frage! Können wir mit der gewöhnlichen Vererbung nicht dasselbe Ergebnis erzielen? Die Messenger
Klasse als übergeordnetes Element und Viber
, Telegram
und WhatsApp
als untergeordnete Elemente. Tatsächlich ist das möglich. Aber es gibt einen Haken. Wie Sie bereits wissen, gibt es in Java keine Mehrfachvererbung. Es gibt jedoch Unterstützung für mehrere Schnittstellen. Eine Klasse kann beliebig viele Schnittstellen implementieren. Stellen Sie sich vor, wir haben eine Smartphone
Klasse, die eine hatApp
Feld, das eine auf dem Smartphone installierte App darstellt.
public class Smartphone {
private App app;
}
Natürlich sind eine App und ein Messenger ähnlich, aber es sind dennoch unterschiedliche Dinge. Es gibt mobile und Desktop-Versionen eines Messengers, aber „App“ steht speziell für eine mobile App. Hier ist der Deal: Wenn wir Vererbung verwenden würden, könnten wir Telegram
der Smartphone
Klasse kein Objekt hinzufügen. Schließlich Telegram
kann die Klasse nicht gleichzeitig erben App
und Messenger
! Und wir haben es bereits vererbt Messenger
und zur Client
Klasse hinzugefügt. Aber die Telegram
Klasse kann problemlos beide Schnittstellen implementieren! Dementsprechend können wir der Client
Klasse ein Telegram
Objekt als übergeben Messenger
und es der Smartphone
Klasse als übergeben App
. So machen Sie das:
public class Telegram implements Application, Messenger {
// ...methods
}
public class Client {
private Messenger messenger;
public Client() {
this.messenger = new Telegram();
}
}
public class Smartphone {
private Application application;
public Smartphone() {
this.application = new Telegram();
}
}
Jetzt verwenden wir die Telegram
Klasse so, wie wir es möchten. An manchen Stellen fungiert es als App
. An anderen Stellen fungiert es als Messenger
. Sie haben sicherlich schon bemerkt, dass Schnittstellenmethoden immer „leer“ sind, also keine Implementierung haben. Der Grund dafür ist einfach: Die Schnittstelle beschreibt das Verhalten, implementiert es aber nicht. „Alle Objekte, die die CanSwim
Schnittstelle implementieren, müssen schwimmen können“: Das ist alles, was uns die Schnittstelle sagt. Die konkrete Art und Weise, wie Fische, Enten und Pferde schwimmen, ist eine Frage für die Fish
, Duck
, undHorse
Klassen, nicht die Schnittstelle. Genauso wie der Senderwechsel eine Aufgabe für den Fernseher ist. Auf der Fernbedienung gibt es dafür lediglich eine Taste. Allerdings erschien in Java 8 eine interessante Ergänzung – Standardmethoden. Ihre Schnittstelle verfügt beispielsweise über 10 Methoden. 9 davon haben unterschiedliche Implementierungen in verschiedenen Klassen, aber eine ist für alle gleich implementiert. Zuvor, vor Java 8, hatten Schnittstellenmethoden überhaupt keine Implementierung: Der Compiler gab sofort einen Fehler aus. Jetzt können Sie so etwas tun:
public interface CanSwim {
public default void swim() {
System.out.println("Swim!");
}
public void eat();
public void run();
}
Mit dem default
Schlüsselwort haben wir eine Schnittstellenmethode mit einer Standardimplementierung erstellt. Wir müssen unsere eigene Implementierung für zwei andere Methoden – eat()
und run()
– in allen Klassen bereitstellen, die implementieren CanSwim
. Bei der Methode müssen wir das nicht tun swim()
: Die Implementierung wird in jeder Klasse gleich sein. Übrigens sind Ihnen in früheren Aufgaben bereits Schnittstellen begegnet, auch wenn Sie es nicht bemerkt haben :) Hier ein anschauliches Beispiel: Sie haben mit den List
und- Set
Schnittstellen gearbeitet! Genauer gesagt haben Sie mit ihren Implementierungen gearbeitet – ArrayList
, LinkedList
, HashSet
usw. Das gleiche Diagramm zeigt deutlich ein Beispiel, bei dem eine Klasse mehrere Schnittstellen gleichzeitig implementiert. LinkedList
Implementiert beispielsweise das List
undDeque
(doppelendige Warteschlange)-Schnittstellen. Sie sind mit der Map
Schnittstelle bzw. deren HashMap
Implementierung vertraut. Dieses Diagramm veranschaulicht übrigens eine Funktion: Schnittstellen können andere Schnittstellen erben. Die SortedMap
Schnittstelle erbt Map
, while Deque
inherits Queue
. Dies ist erforderlich, wenn Sie die Beziehung zwischen Schnittstellen zeigen möchten, wobei eine Schnittstelle eine erweiterte Version einer anderen ist. Betrachten wir ein Beispiel mit der Queue
Schnittstelle. Wir haben es noch nicht überprüftQueues
, aber es ist ziemlich einfach und funktioniert wie eine gewöhnliche Warteschlange in einem Geschäft. Sie können Elemente nur am Ende der Warteschlange hinzufügen und diese nur vom Anfang übernehmen. Irgendwann benötigten die Entwickler eine erweiterte Version der Warteschlange, um an beiden Enden Elemente hinzufügen und entnehmen zu können. Deshalb haben sie eine Deque
Schnittstelle erstellt, bei der es sich um eine doppelendige Warteschlange handelt. Es verfügt über alle Methoden einer gewöhnlichen Warteschlange. Schließlich ist es das übergeordnete Element der doppelseitigen Warteschlange, fügt aber auch neue Methoden hinzu.
GO TO FULL VERSION