Java 9 wird bald kommen und Java-Schnittstellen wie private Methoden werden um weitere Funktionen erweitert. default
Methoden in Schnittstellen wurden in Java 8 hinzugefügt, im Wesentlichen um die Verwendung von Lambdas innerhalb von Sammlungen zu unterstützen ohne die Retro-Kompatibilität mit früheren Versionen der Sprache zu brechen.
In Scala sind Methoden innerhalb von trait
s sehr nützlich. Scala hat jedoch einen anderen Ansatz zur Behandlung von trait
s als Java mit default
Methoden. Denken Sie an die Mehrfachvererbungsauflösung oder die Verwendung von trait
s als mixin s.
Abgesehen von der obigen Verwendung, welche sind die realen Szenarien, in denen die Verwendung von default
Methoden wert ist? Ist in diesen Jahren ein Muster entstanden, das sie benutzt? Welche Probleme kann ich mit dieser Art von Methoden lösen?
Brian Goetz und ich haben einige davon auf unserem JavaOne 2015 Talk, API Design mit Java 8 Lambda und Streams behandelt. Trotz des Titels gibt es am Ende etwas Material über Standardmethoden.
Folien: Ссылка
Video: Ссылка
Ich werde hier zusammenfassen, was wir über Standardmethoden gesagt haben.
Schnittstellenentwicklung
Der primäre Anwendungsfall von Standardmethoden ist die Schnittstellenevolution. Dies ist hauptsächlich die Möglichkeit, Methoden zu Schnittstellen hinzuzufügen, ohne die Rückwärtskompatibilität zu beeinträchtigen. Wie in der Frage erwähnt, wurde dies am häufigsten verwendet, um Methoden hinzuzufügen, die die Konvertierung von Collections in Streams ermöglichen und Lambda-basierte APIs zu Collections hinzufügen.
Es gibt jedoch mehrere andere Anwendungsfälle.
Optionale Methoden
Manchmal sind Schnittstellenmethoden logisch "optional". Ziehen Sie beispielsweise Mutator-Methoden für unveränderbare Sammlungen in Betracht. Natürlich ist eine Implementierung erforderlich, aber normalerweise wird in solchen Fällen eine Ausnahme ausgelöst. Dies kann leicht in einer Standardmethode durchgeführt werden. Implementierungen können die Exception-throwing-Methode erben, wenn sie diese nicht bereitstellen möchten, oder sie können sie überschreiben, wenn sie eine Implementierung bereitstellen möchten. Beispiel: Iterator.remove
.
Convenience-Methoden
Manchmal wird eine Methode für die Bequemlichkeit von Anrufern bereitgestellt, und es gibt eine offensichtliche und optimale Implementierung. Diese Implementierung kann mit einer Standardmethode bereitgestellt werden. Es ist legal, dass eine Implementierung den Standard außer Kraft setzt, aber es gibt im Allgemeinen keinen Grund, weshalb Implementierungen sie normalerweise erben werden. Beispiele: Comparator.reversed
, Spliterator.getExactSizeIfKnown
, Spliterator.hasCharacteristics
. Beachten Sie, dass Spliterator
in Java 8 eingeführt wurde, einschließlich des Standardwerts Methoden, so war dies eindeutig kein Fall von Interface-Evolution.
Einfache Implementierung, die überschrieben werden soll
Eine Standardmethode kann eine einfache, allgemeine Implementierung bereitstellen, die für alle Implementierungen funktioniert, aber das ist wahrscheinlich suboptimal. Dies unterstützt Implementierungen während des anfänglichen Hochfahrens, da sie den Standard erben und für einen korrekten Betrieb sorgen können. Auf lange Sicht werden Implementierungen jedoch wahrscheinlich den Standard überschreiben und eine verbesserte, angepasste Implementierung bereitstellen.
Beispiel: List.sort
. Die Standardimplementierung kopiert die Listenelemente in ein temporäres Array, sortiert das Array und kopiert die Elemente zurück in die Liste. Dies ist eine korrekte Implementierung und manchmal kann sie nicht verbessert werden (zB für LinkedList
). ArrayList
überschreibt jedoch sort
und sortiert das interne Array in-place. Dies vermeidet den Kopieraufwand.
Nun wurde natürlich sort
in Java 8 auf List
und ArrayList
nachgerüstet, so dass die Entwicklung nicht auf diese Weise erfolgte. Aber Sie könnten sich leicht vorstellen, eine neue List
-Implementierung zu erstellen. Sie würden wahrscheinlich die Standardimplementierung sort
erben, während Sie die Grundlagen ordnungsgemäß implementieren. Später könnten Sie einen benutzerdefinierten Sortieralgorithmus implementieren, der auf die interne Datenorganisation Ihrer neuen Implementierung abgestimmt ist.
Nun, ich habe ein realistisches Szenario, in dem ich sie benutzt habe. Hier ist der Kontext: Ich bekomme ein Ergebnis von google maps api
(durch Angabe von Längen- und Breitengrad) in Form eines Array
der Ergebnisse, das wie folgt aussieht:
Dieses Ergebnis enthält einige Informationen, die ich brauche, wie zip-code
oder locality
oder country
. Verschiedene Dienste benötigen unterschiedliche Teile dieser Antwort. Das Parsen dieses Arrays ist dasselbe - Sie müssen nur nach verschiedenen Teilen suchen.
Also habe ich das in einer default
-Methode in interface
:
Jetzt bei der Implementierung der Schnittstelle verwende ich einfach diese Methode.
%Vor%Diese verwendet wird über abstrakte Klassen erstellt. Ich hätte eine private Methode verwenden können, aber diese Schnittstelle wird von vielen anderen Diensten verwendet.
Brian Goetz und ich haben einige davon auf unserem JavaOne 2015 Talk, API Design mit Java 8 Lambda und Streams behandelt. Trotz des Titels gibt es am Ende etwas Material über Standardmethoden.
Folien: Ссылка
Video: Ссылка
Ich werde hier zusammenfassen, was wir über Standardmethoden gesagt haben.
Schnittstellenentwicklung
Der primäre Anwendungsfall von Standardmethoden ist die Schnittstellenevolution. Dies ist hauptsächlich die Möglichkeit, Methoden zu Schnittstellen hinzuzufügen, ohne die Rückwärtskompatibilität zu beeinträchtigen. Wie in der Frage erwähnt, wurde dies am häufigsten verwendet, um Methoden hinzuzufügen, die die Konvertierung von Collections in Streams ermöglichen und Lambda-basierte APIs zu Collections hinzufügen.
Es gibt jedoch mehrere andere Anwendungsfälle.
Optionale Methoden
Manchmal sind Schnittstellenmethoden logisch "optional". Ziehen Sie beispielsweise Mutator-Methoden für unveränderbare Sammlungen in Betracht. Natürlich ist eine Implementierung erforderlich, aber normalerweise wird in solchen Fällen eine Ausnahme ausgelöst. Dies kann leicht in einer Standardmethode durchgeführt werden. Implementierungen können die Exception-throwing-Methode erben, wenn sie diese nicht bereitstellen möchten, oder sie können sie überschreiben, wenn sie eine Implementierung bereitstellen möchten. Beispiel: %code% .
Convenience-Methoden
Manchmal wird eine Methode für die Bequemlichkeit von Anrufern bereitgestellt, und es gibt eine offensichtliche und optimale Implementierung. Diese Implementierung kann mit einer Standardmethode bereitgestellt werden. Es ist legal, dass eine Implementierung den Standard außer Kraft setzt, aber es gibt im Allgemeinen keinen Grund, weshalb Implementierungen sie normalerweise erben werden. Beispiele: %code% , %code% , %code% . Beachten Sie, dass %code% in Java 8 eingeführt wurde, einschließlich des Standardwerts Methoden, so war dies eindeutig kein Fall von Interface-Evolution.
Einfache Implementierung, die überschrieben werden soll
Eine Standardmethode kann eine einfache, allgemeine Implementierung bereitstellen, die für alle Implementierungen funktioniert, aber das ist wahrscheinlich suboptimal. Dies unterstützt Implementierungen während des anfänglichen Hochfahrens, da sie den Standard erben und für einen korrekten Betrieb sorgen können. Auf lange Sicht werden Implementierungen jedoch wahrscheinlich den Standard überschreiben und eine verbesserte, angepasste Implementierung bereitstellen.
Beispiel: %code% . Die Standardimplementierung kopiert die Listenelemente in ein temporäres Array, sortiert das Array und kopiert die Elemente zurück in die Liste. Dies ist eine korrekte Implementierung und manchmal kann sie nicht verbessert werden (zB für %code% ). %code% überschreibt jedoch %code% und sortiert das interne Array in-place. Dies vermeidet den Kopieraufwand.
Nun wurde natürlich %code% in Java 8 auf %code% und %code% nachgerüstet, so dass die Entwicklung nicht auf diese Weise erfolgte. Aber Sie könnten sich leicht vorstellen, eine neue %code% -Implementierung zu erstellen. Sie würden wahrscheinlich die Standardimplementierung %code% erben, während Sie die Grundlagen ordnungsgemäß implementieren. Später könnten Sie einen benutzerdefinierten Sortieralgorithmus implementieren, der auf die interne Datenorganisation Ihrer neuen Implementierung abgestimmt ist.
Manchmal müssen wir die Klasse default %code% für %code% einführen, bei der mehrere Ereignisse ausgelöst werden müssen, aber wir interessieren uns nur für einige der Ereignisse. Beispiel: swing Erstelle jede %code% -Klasse für jede %code% .
Ich fand vor kurzem, dass dies sehr nützlich ist, wenn wir Listener mit Standardmethoden deklarieren, können wir die mittlere Adapterklasse entfernen. zum Beispiel:
%Vor%Java 9 wird bald kommen und Java-Schnittstellen wie private Methoden werden um weitere Funktionen erweitert. %code% Methoden in Schnittstellen wurden in Java 8 hinzugefügt, im Wesentlichen um die Verwendung von Lambdas innerhalb von Sammlungen zu unterstützen ohne die Retro-Kompatibilität mit früheren Versionen der Sprache zu brechen.
In Scala sind Methoden innerhalb von %code% s sehr nützlich. Scala hat jedoch einen anderen Ansatz zur Behandlung von %code% s als Java mit %code% Methoden. Denken Sie an die Mehrfachvererbungsauflösung oder die Verwendung von %code% s als mixin s.
Abgesehen von der obigen Verwendung, welche sind die realen Szenarien, in denen die Verwendung von %code% Methoden wert ist? Ist in diesen Jahren ein Muster entstanden, das sie benutzt? Welche Probleme kann ich mit dieser Art von Methoden lösen?
Zuerst fällt mir die Verwendung von Standardmethoden zur Unterstützung einiger funktionaler Programmiertechniken ein:
%Vor%Beispielverwendung:
%Vor%Sie können ähnliche Bindungsmethoden für die anderen Parameter verwenden, die mit Standardmethoden wie %code% und %code% implementiert werden.
Sie können Standardmethoden verwenden, um die Elternschnittstelle zu dekorieren (wie @holi-java in seiner Antwort erklärt), auch dort gibt es viele Beispiele für das Adaptermuster (currying und binding sind eigentlich Adapter).
Neben der funktionalen Programmierung können Sie Standardmethoden verwenden, um Art , begrenzte Mehrfachvererbung zu unterstützen:
%Vor%Beispiel:
%Vor%Vielleicht nicht das beste Beispiel, aber Sie bekommen die Idee ...
Eine andere Verwendung wären Merkmale:
%Vor%Wenn Sie nun eine Klasse haben, die etwas protokollieren und einige Messwerte melden muss, können Sie Folgendes verwenden:
%Vor%Auch dies ist nicht die bestmögliche Implementierung, sondern nur Beispielcode, um zu sehen, wie Eigenschaften über Standardmethoden verwendet werden können.
Ich möchte manchmal @FunctionalInterface verketten, und wir haben bereits in Funktion mit Standardmethoden zum Verketten der Funktion, zB: %code% , %code% , um den Code elegant zu machen. und das wichtigste ist, dass wir die Teilfunktion später wiederverwenden können, zum Beispiel:
%Vor%Manchmal können wir jedoch das @ FunctionalInterface nicht verketten, da keine Kettenmethoden bereitgestellt wurden. aber ich kann ein anderes schreiben @FunctionalInterface erweitert die ursprünglichen und fügt einige Standardmethoden für die Verkettung hinzu. zum Beispiel:
%Vor%Das ist die Antwort von mir gestern: Mockito returnFirstArg () zu verwenden . Da %code% keine Kettenmethoden hat, führe ich ein weiteres %code% type %code% ein, um Kettenmethoden bereitzustellen.
AntwortPipeline-Klasse
%Vor% Ich möchte manchmal @FunctionalInterface verketten, und wir haben bereits in Funktion mit Standardmethoden zum Verketten der Funktion, zB: compose
, andThen
, um den Code elegant zu machen. und das wichtigste ist, dass wir die Teilfunktion später wiederverwenden können, zum Beispiel:
Manchmal können wir jedoch das @ FunctionalInterface nicht verketten, da keine Kettenmethoden bereitgestellt wurden. aber ich kann ein anderes schreiben @FunctionalInterface erweitert die ursprünglichen und fügt einige Standardmethoden für die Verkettung hinzu. zum Beispiel:
%Vor% Das ist die Antwort von mir gestern: Mockito returnFirstArg () zu verwenden . Da Answer
keine Kettenmethoden hat, führe ich ein weiteres Answer
type AnswerPipeline
ein, um Kettenmethoden bereitzustellen.
AntwortPipeline-Klasse
%Vor%Zuerst fällt mir die Verwendung von Standardmethoden zur Unterstützung einiger funktionaler Programmiertechniken ein:
%Vor%Beispielverwendung:
%Vor% Sie können ähnliche Bindungsmethoden für die anderen Parameter verwenden, die mit Standardmethoden wie bindSecond
und bindThird
implementiert werden.
Sie können Standardmethoden verwenden, um die Elternschnittstelle zu dekorieren (wie @holi-java in seiner Antwort erklärt), auch dort gibt es viele Beispiele für das Adaptermuster (currying und binding sind eigentlich Adapter).
Neben der funktionalen Programmierung können Sie Standardmethoden verwenden, um Art , begrenzte Mehrfachvererbung zu unterstützen:
%Vor%Beispiel:
%Vor%Vielleicht nicht das beste Beispiel, aber Sie bekommen die Idee ...
Eine andere Verwendung wären Merkmale:
%Vor%Wenn Sie nun eine Klasse haben, die etwas protokollieren und einige Messwerte melden muss, können Sie Folgendes verwenden:
%Vor%Auch dies ist nicht die bestmögliche Implementierung, sondern nur Beispielcode, um zu sehen, wie Eigenschaften über Standardmethoden verwendet werden können.
Manchmal müssen wir die Klasse default Adapter
für java.util.EventListener
einführen, bei der mehrere Ereignisse ausgelöst werden müssen, aber wir interessieren uns nur für einige der Ereignisse. Beispiel: swing Erstelle jede *Adapter
-Klasse für jede *Listener
.
Ich fand vor kurzem, dass dies sehr nützlich ist, wenn wir Listener mit Standardmethoden deklarieren, können wir die mittlere Adapterklasse entfernen. zum Beispiel:
%Vor%Tags und Links java-8 design-patterns interface java-9 default-method