In Java 8 können Schnittstellen implementierte Methoden, statische Methoden und sogenannte "Standard" -Methoden enthalten (die von den implementierenden Klassen nicht überschrieben werden müssen).
Aus meiner (wahrscheinlich naiven) Sicht bestand keine Notwendigkeit, solche Schnittstellen zu verletzen. Schnittstellen waren schon immer ein Vertrag, den Sie erfüllen müssen, und dies ist ein sehr einfaches und reines Konzept. Jetzt ist es eine Mischung aus mehreren Dingen. Meiner Meinung nach:
- statische Methoden gehören nicht zu Schnittstellen. Sie gehören zu den Gebrauchsklassen.
- "Standard" -Methoden sollten in Interfaces überhaupt nicht erlaubt sein. Zu diesem Zweck können Sie immer eine abstrakte Klasse verwenden.
Zusamenfassend:
Vor Java 8:
- Sie können abstrakte und reguläre Klassen verwenden, um statische und Standardmethoden bereitzustellen. Die Rolle der Schnittstellen ist klar.
- Alle Methoden in einer Schnittstelle sollten durch die Implementierung von Klassen überschrieben werden.
- Sie können einer Schnittstelle keine neue Methode hinzufügen, ohne alle Implementierungen zu ändern, aber das ist eigentlich eine gute Sache.
Nach Java 8:
- Es gibt praktisch keinen Unterschied zwischen einer Schnittstelle und einer abstrakten Klasse (außer Mehrfachvererbung). Tatsächlich können Sie eine reguläre Klasse mit einer Schnittstelle emulieren.
- Bei der Programmierung der Implementierungen vergessen Programmierer möglicherweise, die Standardmethoden zu überschreiben.
- Es liegt ein Kompilierungsfehler vor, wenn eine Klasse versucht, zwei oder mehr Schnittstellen mit einer Standardmethode mit derselben Signatur zu implementieren.
- Durch Hinzufügen einer Standardmethode zu einer Schnittstelle erbt jede implementierende Klasse automatisch dieses Verhalten. Einige dieser Klassen wurden möglicherweise nicht mit dieser neuen Funktionalität entwickelt, was zu Problemen führen kann. Wenn beispielsweise jemand
default void foo()
einer Schnittstelle eine neue Standardmethode hinzufügtIx
, wird die Klasse , die eine private Methode mit derselben SignaturCx
implementiertIx
undfoo
besitzt, nicht kompiliert.
Was sind die Hauptgründe für diese großen Änderungen und welche neuen Vorteile (falls vorhanden) bringen sie mit sich?
java
programming-languages
interfaces
java8
Herr Smith
quelle
quelle
@Deprecated
Kategorie! statische Methoden sind aufgrund von Unwissenheit und Faulheit eines der am häufigsten missbrauchten Konstrukte in Java. Viele statische Methoden bedeuten normalerweise inkompetente Programmierer, erhöhen die Kopplung um mehrere Größenordnungen und sind ein Albtraum für Unit-Tests und Refactors, wenn Sie erkennen, warum sie eine schlechte Idee sind!Antworten:
Ein gutes motivierendes Beispiel für Standardmethoden ist die Java-Standardbibliothek, über die Sie jetzt verfügen
Anstatt von
Ich glaube nicht, dass sie das sonst ohne mehr als eine identische Implementierung von getan hätten
List.sort
.quelle
IEnumerable<Byte>.Append
sie hinzu, rufen Sie an und erklären SieCount
mir, wie Erweiterungsmethoden das Problem lösen. WennCountIsKnown
undCount
waren Mitglieder vonIEnumerable<T>
, könnte die Rückkehr vonAppend
werben,CountIsKnown
wenn die konstituierenden Sammlungen dies taten, aber ohne solche Methoden ist das nicht möglich.Die richtige Antwort findet sich tatsächlich in der Java-Dokumentation , in der es heißt:
Dies hat Java schon seit langem geschmerzt, da sich Schnittstellen nach ihrer Veröffentlichung in der Regel nicht mehr weiterentwickeln ließen. (Der Inhalt der Dokumentation bezieht sich auf das Dokument, auf das Sie in einem Kommentar verwiesen haben: Schnittstellenentwicklung über virtuelle Erweiterungsmethoden .) Darüber hinaus kann eine schnelle Übernahme neuer Funktionen (z. B. Lambdas und die neuen Stream-APIs) nur durch Erweiterung der API erfolgen Schnittstellen für vorhandene Sammlungen und Bereitstellung von Standardimplementierungen. Wenn Sie die Binärkompatibilität aufheben oder neue APIs einführen, vergehen einige Jahre, bis die wichtigsten Funktionen von Java 8 gemeinsam genutzt werden.
Der Grund für das Zulassen statischer Methoden in Interfaces geht aus der Dokumentation erneut hervor: [t] Dies erleichtert Ihnen das Organisieren von Hilfsmethoden in Ihren Bibliotheken. Sie können statische Methoden, die für eine Schnittstelle spezifisch sind, in derselben Schnittstelle und nicht in einer separaten Klasse aufbewahren. Mit anderen Worten, statische Dienstprogrammklassen wie
java.util.Collections
jetzt (endlich) können allgemein (natürlich nicht immer ) als Antimuster betrachtet werden . Ich vermute, dass das Hinzufügen von Unterstützung für dieses Verhalten nach der Implementierung virtueller Erweiterungsmethoden trivial war, da dies sonst wahrscheinlich nicht erfolgt wäre.Ein Beispiel dafür, wie diese neuen Funktionen von Nutzen sein können, ist die Betrachtung einer Klasse, die mich in letzter Zeit geärgert hat
java.util.UUID
. Es bietet keine Unterstützung für die UUID-Typen 1, 2 oder 5 und kann nicht ohne weiteres geändert werden. Es hängt auch an einem vordefinierten Zufallsgenerator, der nicht überschrieben werden kann. Das Implementieren von Code für die nicht unterstützten UUID-Typen erfordert entweder eine direkte Abhängigkeit von einer Drittanbieter-API anstelle einer Schnittstelle oder die Verwaltung des Konvertierungscodes und die damit verbundenen Kosten für die zusätzliche Garbage Collection. Bei statischen Methoden,UUID
könnte stattdessen als eine Schnittstelle definiert, so dass echte Fremd Implementierungen der fehlenden Stücke. (WennUUID
wir ursprünglich als Schnittstelle definiert wären, hätten wir wahrscheinlich eine Art klobigesUuidUtil
Klasse mit statischen Methoden, was auch schrecklich wäre.) Viele der Java-Kern-APIs werden dadurch herabgesetzt, dass sie sich nicht auf Schnittstellen stützen, aber seit Java 8 hat sich die Anzahl der Entschuldigungen für dieses schlechte Verhalten dankenswerterweise verringert.Es ist nicht richtig zu sagen, dass [t] hier praktisch kein Unterschied zwischen einer Schnittstelle und einer abstrakten Klasse besteht , da abstrakte Klassen einen Zustand haben können (dh Felder deklarieren), während Schnittstellen dies nicht können. Es ist daher nicht gleichbedeutend mit Mehrfachvererbung oder sogar einer Vererbung im Mixin-Stil. Richtige Mixins (wie Groovy 2.3s Merkmale ) haben Zugriff auf den Status. (Groovy unterstützt auch statische Erweiterungsmethoden.)
Meiner Meinung nach ist es auch keine gute Idee, Dovals Beispiel zu folgen . Eine Schnittstelle soll einen Vertrag definieren, aber nicht den Vertrag erzwingen. (Auf jeden Fall nicht in Java.) Die ordnungsgemäße Überprüfung einer Implementierung liegt in der Verantwortung einer Testsuite oder eines anderen Tools. Das Definieren von Verträgen könnte mit Anmerkungen erfolgen, und OVal ist ein gutes Beispiel, aber ich weiß nicht, ob es Einschränkungen unterstützt, die für Schnittstellen definiert wurden. Ein solches System ist machbar, auch wenn es derzeit noch kein System gibt. (Zu den Strategien gehört die Anpassung zur Kompilierungszeit
javac
über den AnmerkungsprozessorAPI- und Laufzeit-Bytecode-Generierung.) Im Idealfall werden Verträge zur Kompilierungszeit und im schlimmsten Fall mit einer Testsuite erzwungen, aber ich verstehe, dass die Laufzeiterzwingung verpönt ist. Ein weiteres interessantes Tool zur Unterstützung der Vertragsprogrammierung in Java ist das Checker Framework .quelle
default
Methoden nicht überschreibenequals
könnenhashCode
undtoString
. Eine sehr aussagekräftige Kosten-Nutzen-Analyse, warum dies nicht zulässigequals
und eine einzigehashCode
Methode hat, da es zwei verschiedene Arten von Gleichheit gibt, die Sammlungen möglicherweise testen müssen, und Elemente, die mehrere Schnittstellen implementieren, möglicherweise mit widersprüchlichen vertraglichen Anforderungen verbunden sind. Die Verwendung von Listen, die sich nicht alshashMap
Schlüssel ändern, ist hilfreich. Manchmal ist es jedoch hilfreich, Sammlungen in einer Reihenfolge zu speichern,hashMap
die eher auf der Äquivalenz als auf dem aktuellen Status basiert. [Äquivalenz impliziert Übereinstimmungsstatus und Unveränderlichkeit ] .Weil Sie nur eine Klasse erben können. Wenn Sie zwei Schnittstellen haben, deren Implementierungen so komplex sind, dass Sie eine abstrakte Basisklasse benötigen, schließen sich diese beiden Schnittstellen in der Praxis gegenseitig aus.
Die Alternative besteht darin, diese abstrakten Basisklassen in eine Sammlung statischer Methoden zu konvertieren und alle Felder in Argumente umzuwandeln. Das würde jedem Implementierer der Schnittstelle erlauben, die statischen Methoden aufzurufen und die Funktionalität zu erhalten, aber es ist eine Menge Boilerplate in einer Sprache, die bereits viel zu ausführlich ist.
Als ein motivierendes Beispiel dafür, warum es nützlich sein kann, Implementierungen in Schnittstellen bereitzustellen, betrachten Sie diese Stack-Schnittstelle:
Es kann nicht garantiert werden, dass bei der Implementierung der Schnittstelle
pop
eine Ausnahme ausgelöst wird, wenn der Stapel leer ist. Wir könnten diese Regel durchsetzen, indem wir siepop
in zwei Methodenpublic final
aufteilen : eine Methode, die den Vertrag erzwingt, und eineprotected abstract
Methode, die das eigentliche Poppen durchführt.Wir stellen nicht nur sicher, dass alle Implementierungen den Vertrag einhalten, sondern haben sie auch von der Notwendigkeit befreit, zu überprüfen, ob der Stack leer ist und die Ausnahme auszulösen. Es ist ein großer Gewinn! ... bis auf die Tatsache, dass wir das Interface in eine abstrakte Klasse verwandeln mussten. In einer Sprache mit einfacher Vererbung bedeutet dies einen großen Verlust an Flexibilität. Dadurch schließen sich Ihre potenziellen Schnittstellen gegenseitig aus. Implementierungen bereitstellen zu können, die nur auf den Schnittstellenmethoden selbst beruhen, würde das Problem lösen.
Ich bin nicht sicher, ob der Ansatz von Java 8 zum Hinzufügen von Methoden zu Schnittstellen das Hinzufügen endgültiger Methoden oder geschützter abstrakter Methoden zulässt , aber ich weiß, dass die D-Sprache dies zulässt und native Unterstützung für Design by Contract bietet . Diese Technik birgt keine Gefahr, da sie
pop
endgültig ist und daher von keiner implementierenden Klasse außer Kraft gesetzt werden kann.In Bezug auf Standardimplementierungen überschreibbarer Methoden gehe ich davon aus, dass Standardimplementierungen, die zu den Java-APIs hinzugefügt werden, nur vom Vertrag der Schnittstelle abhängen, zu der sie hinzugefügt wurden. Daher verhält sich jede Klasse, die die Schnittstelle korrekt implementiert, auch mit den Standardimplementierungen korrekt.
Außerdem,
Dies ist nicht ganz richtig, da Sie in einer Schnittstelle keine Felder deklarieren können. Jede Methode, die Sie in eine Schnittstelle schreiben, kann sich nicht auf Implementierungsdetails stützen.
Betrachten Sie als Beispiel für statische Methoden in Schnittstellen Dienstprogrammklassen wie Sammlungen in der Java-API. Diese Klasse existiert nur, weil diese statischen Methoden nicht in ihren jeweiligen Schnittstellen deklariert werden können.
Collections.unmodifiableList
hätte genauso gut in derList
Oberfläche deklariert werden können, und es wäre leichter zu finden gewesen.quelle
Stack
Schnittstelle und möchten sicherstellen, dass beimpop
Aufrufen mit einem leeren Stapel eine Ausnahme ausgelöst wird. Angesichts abstrakter Methodenboolean isEmpty()
undprotected T pop_impl()
könnten Sie implementieren.final T pop() { isEmpty()) throw PopException(); else return pop_impl(); }
Dies erzwingt den Vertrag für ALLE Implementierer.static
.Vielleicht bestand die Absicht darin, die Möglichkeit zum Erstellen von Mixin- Klassen bereitzustellen, indem die Notwendigkeit, statische Informationen oder Funktionen über eine Abhängigkeit einzufügen , ersetzt wurde.
Diese Idee scheint damit zu tun zu haben, wie Sie Erweiterungsmethoden in C # verwenden können, um den Schnittstellen implementierte Funktionen hinzuzufügen.
quelle
list.sort(ordering);
Formulars.IEnumerable
Schnittstelle in C # ansehen , können Sie sehen, wie das Implementieren von Erweiterungsmethoden für diese Schnittstelle (wie es derLINQ to Objects
Fall ist) die Funktionalität für jede implementierte Klasse hinzufügtIEnumerable
. Das habe ich mit Funktionalität gemeint.Die beiden Hauptziele, die ich in
default
Methoden sehe (einige Anwendungsfälle dienen beiden Zwecken):Wenn es nur um den zweiten Zweck ginge, würde man das nicht in einer brandneuen Benutzeroberfläche wie sehen
Predicate
. Alle mit@FunctionalInterface
Annotationen versehenen Schnittstellen müssen genau eine abstrakte Methode haben, damit ein Lambda sie implementieren kann. Hinzugefügtdefault
Methoden wieand
,or
,negate
sind nur Dienstprogramm, und Sie sind nicht zu überschreiben , sie soll. Doch manchmal statische Methoden täten besser .Was die Erweiterung bestehender Schnittstellen angeht - selbst dort sind einige neue Methoden nur Syntaxzucker. Methoden
Collection
wiestream
,forEach
,removeIf
- im Grunde, es ist nur Dienstprogramm , das Sie nicht außer Kraft setzen müssen. Und dann gibt es Methoden wiespliterator
. Die Standardimplementierung ist suboptimal, aber zumindest der Code wird kompiliert. Greifen Sie nur dann darauf zurück, wenn Ihr Interface bereits veröffentlicht und weit verbreitet ist.Was die
static
Methoden, ich denke , es die andere decken ganz gut: Es ermöglicht die Schnittstelle ihre eigene Utility - Klasse zu sein. Vielleicht könnten wir unsCollections
in Javas Zukunft davonmachen?Set.empty()
würde rocken.quelle