Warum ist "final" in Java 8-Schnittstellenmethoden nicht zulässig?


335

Eine der nützlichsten Funktionen von Java 8 sind die neuen defaultMethoden für Schnittstellen. Es gibt im Wesentlichen zwei Gründe (es kann andere geben), warum sie eingeführt wurden:

Aus Sicht eines API-Designers hätte ich gerne andere Modifikatoren für Schnittstellenmethoden verwenden können, z final. Dies ist nützlich, wenn Sie praktische Methoden hinzufügen, um "versehentliche" Überschreibungen bei der Implementierung von Klassen zu verhindern:

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

Das Obige ist bereits gängige Praxis, wenn Senderes sich um eine Klasse handelt:

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

Nun, defaultund finalwidersprechen offensichtlich Schlüsselwörtern, aber das Standardschlüsselwort selbst wäre nicht unbedingt erforderlich gewesen , daher gehe ich davon aus, dass dieser Widerspruch absichtlich ist, um die subtilen Unterschiede zwischen "Klassenmethoden mit Körper" (nur Methoden) und "Schnittstelle" widerzuspiegeln Methoden mit Körper " (Standardmethoden), dh Unterschiede, die ich noch nicht verstanden habe.

Zu einem bestimmten Zeitpunkt wurde die Unterstützung für Modifikatoren wie staticund finalfür Schnittstellenmethoden noch nicht vollständig untersucht, unter Berufung auf Brian Goetz :

Der andere Teil ist, wie weit wir gehen werden, um Tools zum Erstellen von Klassen in Schnittstellen wie endgültigen Methoden, privaten Methoden, geschützten Methoden, statischen Methoden usw. zu unterstützen. Die Antwort lautet: Wir wissen es noch nicht

Seit dieser Zeit Ende 2011 wurde offensichtlich die Unterstützung für staticMethoden in Schnittstellen hinzugefügt. Dies hat den JDK-Bibliotheken selbst, wie z. B. mit, einen großen Mehrwert gebracht Comparator.comparing().

Frage:

Was ist der Grund final(und auch static final), warum es nie zu Java 8-Schnittstellen gekommen ist?


10
Es tut mir leid, eine nasse Decke zu sein, aber die einzige Möglichkeit, die im Titel geäußerte Frage im Rahmen von SO zu beantworten, ist ein Zitat von Brian Goetz oder der JSR-Expertengruppe. Ich verstehe, dass BG eine öffentliche Diskussion angefordert hat, aber genau das verstößt gegen die Bestimmungen von SO, da es "hauptsächlich auf Meinungen basiert". Es scheint mir, dass die Verantwortung hier geduckt wird. Es ist die Aufgabe der Expertengruppe und des umfassenderen Java-Community-Prozesses, die Diskussion anzuregen und Rationalisierungen zu finden. Nicht SOs. Ich stimme also dafür, als "hauptsächlich meinungsbasiert" zu schließen.
Marquis von Lorne

2
Wie wir alle wissen, wird finalverhindert , dass eine Methode überschrieben wird, und wenn Sie sehen, wie Sie Methoden überschreiben MÜSSEN, die von Schnittstellen geerbt wurden, sehe ich nicht, warum es sinnvoll wäre, sie endgültig zu machen. Es sei denn, es sollte bedeuten, dass die Methode endgültig ist, nachdem sie einmal überschrieben wurde. In diesem Fall gibt es möglicherweise qas Schwierigkeiten? Wenn ich dieses Recht nicht verstehe, lass mich bitte kmow. Scheint interessant
Vince Emigh

22
@EJP: "Wenn ich das kann, kannst du es auch und beantworte deine eigene Frage, die daher nicht gestellt werden müsste." Das würde für so ziemlich alle Fragen in diesem Forum gelten, oder? Ich könnte immer 5 Stunden lang selbst ein Thema googeln und dann etwas lernen, das alle anderen auf die gleiche harte Weise lernen müssen. Oder wir warten noch ein paar Minuten, bis jemand eine noch bessere Antwort gibt, von der jeder in Zukunft (einschließlich der bisher 12 Upvotes und 8 Sterne) profitieren kann, weil SO bei Google so gut referenziert ist. Also ja. Diese Frage kann perfekt in das Q & A-Formular von SO passen.
Lukas Eder

5
@VinceEmigh " ... sehen, wie Sie von Schnittstellen geerbte Methoden überschreiben MÜSSEN ... " Das ist in Java 8 nicht der Fall. Mit Java 8 können Sie Methoden in Schnittstellen implementieren, was bedeutet, dass Sie sie bei der Implementierung nicht implementieren müssen Klassen. Hier finalkönnte verhindert werden, dass implementierende Klassen die Standardimplementierung einer Schnittstellenmethode überschreiben.
awksp

28
@EJP du weißt nie: Brian Goetz kann antworten !
Assylien

Antworten:


418

Diese Frage hängt bis zu einem gewissen Grad mit zusammen Was ist der Grund, warum "synchronisiert" in Java 8-Schnittstellenmethoden nicht zulässig ist?

Das Wichtigste, was Sie über Standardmethoden verstehen sollten, ist, dass das primäre Entwurfsziel darin besteht Entwicklung der Benutzeroberfläche ist und nicht "Schnittstellen in (mittelmäßige) Merkmale verwandeln". Obwohl es einige Überschneidungen zwischen den beiden gibt und wir versucht haben, uns an die letzteren anzupassen, wo sie den ersteren nicht im Wege standen, sind diese Fragen am besten zu verstehen, wenn man sie in diesem Licht betrachtet. (Hinweis zu , dass Klassenmethoden sind verschieden von Interface - Methoden zu gehen, egal , was die Absicht, auf Grund der Tatsache , dass Interface - Methoden können mehrfach vererbt werden.)

Die Grundidee einer Standardmethode lautet: Es handelt sich um eine Schnittstellenmethode mit einer Standardimplementierung, und eine abgeleitete Klasse kann eine spezifischere Implementierung bereitstellen. Und da das Design Center die Entwicklung von Schnittstellen war, war es ein kritisches Entwurfsziel, dass Standardmethoden zu Schnittstellen hinzugefügt werden können nach der Tat in einer Quelle-kompatibel und binärkompatibel Weise.

Die zu einfache Antwort auf "Warum nicht endgültige Standardmethoden?" Lautet, dass der Body dann nicht einfach die Standardimplementierung, sondern die einzige Implementierung wäre. Das ist zwar eine etwas zu einfache Antwort, gibt uns aber einen Hinweis darauf, dass die Frage bereits in eine fragwürdige Richtung geht.

Ein weiterer Grund, warum endgültige Schnittstellenmethoden fragwürdig sind, besteht darin, dass sie für Implementierer unmögliche Probleme verursachen. Angenommen, Sie haben:

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

Hier ist alles gut; Cerbt foo()von A. Angenommen B, es wird eine fooMethode mit einer Standardeinstellung geändert :

interface B { 
    default void foo() { ... }
}

Wenn wir jetzt zur Neukompilierung gehen, Cteilt uns der Compiler mit, dass er nicht weiß, für welches Verhalten er erben soll foo(), und dass Cer es überschreiben muss (und A.super.foo()möglicherweise delegieren kann, wenn er dasselbe Verhalten beibehalten möchte). Aber was ist, wenn Bhatte seinen Standard gemacht finalund Asteht nicht unter der Kontrolle des Autors von C? Jetzt Cist unwiederbringlich gebrochen; Es kann nicht kompiliert werden, ohne es zu überschreiben foo(), aber es kann nicht überschrieben werdenfoo() wenn es endgültig war B.

Dies ist nur ein Beispiel, aber der Punkt ist, dass die Endgültigkeit von Methoden wirklich ein Werkzeug ist, das in der Welt der Einzelvererbungsklassen (im Allgemeinen, die den Zustand mit dem Verhalten koppeln) sinnvoller ist als Schnittstellen, die lediglich zum Verhalten beitragen und multipliziert werden können vererbt. Es ist zu schwer zu überlegen, "welche anderen Schnittstellen in den späteren Implementierer eingemischt werden könnten", und das Zulassen, dass eine Schnittstellenmethode endgültig ist, würde diese Probleme wahrscheinlich verursachen (und sie würden nicht die Person in die Luft jagen, die die Schnittstelle geschrieben hat, sondern die armer Benutzer, der versucht, es zu implementieren.)

Ein weiterer Grund, sie nicht zuzulassen, ist, dass sie nicht bedeuten würden, was Sie denken, dass sie bedeuten. Eine Standardimplementierung wird nur berücksichtigt, wenn die Klasse (oder ihre Oberklassen) keine Deklaration (konkret oder abstrakt) der Methode bereitstellt. Wenn eine Standardmethode endgültig wäre, aber eine Oberklasse die Methode bereits implementiert hätte, würde die Standardeinstellung ignoriert, was wahrscheinlich nicht das ist, was der Standardautor erwartet hatte, als er sie für endgültig erklärte. (Dieses Vererbungsverhalten spiegelt das Design Center für Standardmethoden - Schnittstellenentwicklung wider. Es sollte möglich sein, vorhandenen Schnittstellen, die bereits implementiert sind, eine Standardmethode (oder eine Standardimplementierung zu einer vorhandenen Schnittstellenmethode) hinzuzufügen, ohne die zu ändern Verhalten vorhandener Klassen, die die Schnittstelle implementieren,


88
Es ist fantastisch zu sehen, wie Sie Fragen zu den neuen Sprachfunktionen beantworten! Es ist sehr hilfreich, die Absichten und Details des Designs zu klären, wenn Sie herausfinden, wie wir die neuen Funktionen verwenden sollen. Tragen andere Personen, die am Design beteiligt waren, zu SO bei - oder machen Sie das einfach selbst? Ich werde Ihre Antworten unter dem Java-8-Tag verfolgen - ich würde gerne wissen, ob es andere Leute gibt, die das Gleiche tun, damit ich ihnen auch folgen kann.
Geschoren

10
@Shorn Stuart Marks war im Java-8-Tag aktiv. Jeremy Manson hat in der Vergangenheit gepostet. Ich erinnere mich auch, dass ich Nachrichten von Joshua Bloch gesehen habe, sie aber momentan nicht finden kann.
Assylias

1
Herzlichen Glückwunsch zu den Standardschnittstellenmethoden, die viel eleganter sind, um das zu erreichen, was C # mit seinen schlecht konzipierten und ziemlich hässlich implementierten Erweiterungsmethoden tut. Was diese Frage betrifft, so ist die Antwort auf die Unmöglichkeit, einen Namenskonflikt zu lösen, eine Lösung für das Problem, aber die übrigen Gründe waren nicht überzeugende Philologie. (Wenn ich möchte, dass eine Schnittstellenmethode endgültig ist, sollten Sie davon ausgehen, dass ich meine eigenen verdammt guten Gründe haben muss, um zu verhindern, dass jemand eine andere Implementierung als meine bereitstellt.)
Mike Nakis

1
Die Lösung von Namenskonflikten hätte jedoch auf ähnliche Weise wie in C # erreicht werden können, indem der Deklaration der Implementierungsmethode der Name der bestimmten Schnittstelle hinzugefügt wurde, die implementiert wird. Was ich von all dem mit nach Hause nehme, ist, dass Standardschnittstellenmethoden in Java nicht endgültig sein können, da dies zusätzliche Änderungen an der Syntax erforderlich gemacht hätte, bei denen Sie sich nicht gut gefühlt haben. (Vielleicht zu scharf?)
Mike Nakis

18
@ Trying Abstract-Klassen sind immer noch die einzige Möglichkeit, den Status einzuführen oder die Kernobjektmethoden zu implementieren. Standardmethoden sind für reines Verhalten ; abstrakte Klassen sind für Verhalten in Verbindung mit Zustand.
Brian Goetz

43

In der Lambda-Mailingliste gibt es viele Diskussionen darüber . Eine davon, die eine Menge Diskussionen über all diese Dinge zu enthalten scheint, ist die folgende: Auf Sichtbarkeit der verschiedenen Schnittstellenmethoden (war Final Defenders) .

In dieser Diskussion stellt Talden, der Autor der ursprünglichen Frage , etwas, das Ihrer Frage sehr ähnlich ist:

Die Entscheidung, alle Schnittstellenmitglieder öffentlich zu machen, war in der Tat eine unglückliche Entscheidung. Dass jede Verwendung der Schnittstelle im internen Design private Details der Implementierung enthüllt, ist eine große Sache.

Es ist schwierig, das Problem zu beheben, ohne der Sprache obskure oder kompatible Nuancen hinzuzufügen. Eine Kompatibilitätsunterbrechung dieser Größenordnung und potenziellen Subtilität wäre unverständlich, sodass eine Lösung existieren muss, die den vorhandenen Code nicht beschädigt.

Könnte die Wiedereinführung des Schlüsselworts 'package' als Zugriffsspezifizierer sinnvoll sein. Das Fehlen eines Spezifizierers in einer Schnittstelle würde einen öffentlichen Zugriff implizieren, und das Fehlen eines Spezifizierers in einer Klasse impliziert einen Paketzugriff. Welche Spezifizierer in einer Schnittstelle sinnvoll sind, ist unklar - insbesondere wenn wir zur Minimierung des Wissensaufwands für Entwickler sicherstellen müssen, dass Zugriffsspezifizierer in Klasse und Schnittstelle dasselbe bedeuten, wenn sie vorhanden sind.

Ohne Standardmethoden hätte ich spekuliert, dass der Bezeichner eines Mitglieds in einer Schnittstelle mindestens so sichtbar sein muss wie die Schnittstelle selbst (damit die Schnittstelle tatsächlich in allen sichtbaren Kontexten implementiert werden kann) - mit Standardmethoden, die dies nicht sind so sicher.

Gab es eine klare Mitteilung darüber, ob dies überhaupt eine mögliche Diskussion im Rahmen ist? Wenn nicht, sollte es woanders stattfinden.

Schließlich war Brian Goetz 'Antwort :

Ja, das wird bereits untersucht.

Lassen Sie mich jedoch einige realistische Erwartungen setzen - Sprach- / VM-Funktionen haben eine lange Vorlaufzeit, selbst solche, die trivial erscheinen. Die Zeit, neue Ideen für Sprachfunktionen für Java SE 8 vorzuschlagen, ist fast vorbei.

Daher wurde es höchstwahrscheinlich nie implementiert, da es nie Teil des Geltungsbereichs war. Es wurde nie rechtzeitig vorgeschlagen, darüber nachzudenken.

In einer weiteren hitzigen Diskussion über die Methoden des endgültigen Verteidigers zu diesem Thema sagte Brian erneut :

Und Sie haben genau das bekommen, was Sie sich gewünscht haben. Genau das fügt diese Funktion hinzu - mehrfache Vererbung von Verhalten. Natürlich verstehen wir, dass Menschen sie als Eigenschaften verwenden werden. Und wir haben hart gearbeitet, um sicherzustellen, dass das von ihnen angebotene Vererbungsmodell einfach und sauber genug ist, damit die Menschen in einer Vielzahl von Situationen gute Ergebnisse erzielen können. Gleichzeitig haben wir uns entschieden, sie nicht über die Grenzen dessen hinauszuschieben, was einfach und sauber funktioniert, und das führt in einigen Fällen zu Reaktionen, dass Sie nicht weit genug gegangen sind. Aber wirklich, der größte Teil dieses Fadens scheint zu meckern, dass das Glas nur zu 98% voll ist. Ich nehme die 98% und mache weiter!

Dies bestätigt meine Theorie, dass es einfach nicht Teil des Umfangs oder des Entwurfs war. Sie haben genügend Funktionen bereitgestellt, um die Probleme der API-Evolution zu lösen.


4
Ich sehe, dass ich heute Morgen den alten Namen "Verteidigungsmethoden" in meine Googeln-Odyssee hätte aufnehmen sollen. +1 für das Ausgraben.
Marco13

1
Nettes Ausgraben historischer Fakten. Ihre Schlussfolgerungen stimmen gut mit der offiziellen Antwort
Lukas Eder

Ich verstehe nicht, warum es die Abwärtskompatibilität brechen würde. Finale war vorher nicht erlaubt. Sie erlauben jetzt private, aber nicht geschützte. myeh ... private kann nicht implementieren ... eine Schnittstelle, die eine andere Schnittstelle erweitert, implementiert möglicherweise Teile einer übergeordneten Schnittstelle, muss jedoch die Überlastungsfunktion anderen zugänglich machen ...
mmm

17

Es wird schwer sein , zu finden und zu identifizieren „ die“ Antwort, für die resons in den Kommentaren von @EJP erwähnt: Es gibt ungefähr 2 (+/- 2) Menschen in der Welt, die der definitive Antwort geben kann überhaupt . Und im Zweifel könnte die Antwort nur so etwas wie "Die Unterstützung der endgültigen Standardmethoden schien die Mühe der Umstrukturierung der internen Anrufauflösungsmechanismen nicht wert zu sein". Dies ist natürlich Spekulation, aber es wird zumindest durch subtile Beweise gestützt, wie diese Aussage (von einer der beiden Personen) in der OpenJDK-Mailingliste :

"Ich nehme an, wenn" endgültige Standard "-Methoden zulässig wären, müssten sie möglicherweise von der internen Aufrufspezialspezifikation in die vom Benutzer sichtbare Aufrufschnittstelle umgeschrieben werden."

und triviale Tatsachen wie diese, dass eine Methode einfach nicht als (wirklich) endgültige Methode betrachtet wird, wenn es sich um eine defaultMethode handelt, wie sie derzeit in der Methode :: is_final_method implementiert ist Methode im OpenJDK .

Weitere wirklich "autorisierende" Informationen sind in der Tat schwer zu finden, selbst bei übermäßigen Websuchen und durch Lesen von Commit-Protokollen. Ich dachte, dass es mit möglichen Mehrdeutigkeiten während der Auflösung von Schnittstellenmethodenaufrufen mit der invokeinterfaceAnweisung und und Klassenmethodenaufrufen zusammenhängen könnte, die der invokevirtualAnweisung entsprechen: Für die invokevirtualAnweisung kann es eine einfache vtable- Suche geben, da die Methode entweder vererbt werden muss aus einer Oberklasse oder direkt von der Klasse implementiert. Im Gegensatz dazu muss ein invokeinterfaceAufruf die jeweilige Anrufstelle untersuchen, um herauszufinden, auf welche Schnittstelle sich dieser Aufruf tatsächlich bezieht (dies wird ausführlicher in den erläutert Seite InterfaceCalls des HotSpot-Wikis ). Jedoch,finalMethoden werden entweder überhaupt nicht in die vtable eingefügt oder ersetzen vorhandene Einträge in der vtable (siehe klassVtable.cpp. Zeile 333 ), und in ähnlicher Weise ersetzen Standardmethoden vorhandene Einträge in der vtable (siehe klassVtable.cpp, Zeile 202 ). . Der eigentliche Grund (und damit die Antwort) muss also tiefer in den (ziemlich komplexen) Methodenauflösungsmechanismen verborgen sein, aber vielleicht werden diese Referenzen dennoch als hilfreich angesehen, sei es nur für andere, die es schaffen, die tatsächliche Antwort abzuleiten davon.


Danke für den interessanten Einblick. Das Stück von John Rose ist eine interessante Spur. Ich bin jedoch immer noch nicht mit @EJP einverstanden. Schauen Sie sich als Gegenbeispiel meine Antwort auf eine sehr interessante, sehr ähnliche Frage von Peter Lawrey an . Es ist möglich, historische Fakten herauszufinden, und ich bin immer froh, sie hier auf Stack Overflow zu finden (wo sonst?). Natürlich ist Ihre Antwort immer noch spekulativ, und ich bin nicht zu 100% davon überzeugt, dass Details zur JVM-Implementierung der letzte Grund (Wortspiel beabsichtigt) für die JLS sind, auf die eine oder andere Weise niedergeschrieben zu werden ...
Lukas Eder

@LukasEder Sicher, diese Art von Fragen sind interessant und passen meiner Meinung nach in das Q & A-Muster. Ich denke, es gibt zwei entscheidende Punkte, die die Disputation hier verursacht haben: Der erste ist, dass Sie nach "dem Grund" gefragt haben. Dies kann in vielen Fällen einfach nicht offiziell dokumentiert werden. ZB gibt es keinen "Grund", der in der JLS erwähnt wird, warum es keine nicht signierten ints gibt, aber siehe stackoverflow.com/questions/430346 ... ...
Marco13

... ... Das zweite ist, dass Sie nur nach "maßgeblichen Zitaten" gefragt haben , was die Anzahl der Personen, die es wagen, eine Antwort zu schreiben, von "ein paar Dutzend" auf ... "ungefähr Null" reduziert. Abgesehen davon bin ich mir nicht sicher, wie die Entwicklung der JVM und das Schreiben des JLS miteinander verwoben sind, dh inwieweit die Entwicklung Einfluss darauf hat, was in das JLS geschrieben wird, aber ... ich werde hier jegliche Spekulation vermeiden; -)
Marco13

1
Ich ruhe mich immer noch aus. Sehen Sie, wen meine beantwortet hat andere Frage :-) Es wird nun immer klar sein , mit einer autoritativen Antwort, hier auf Stack - Überlauf , warum es wurde beschlossen , nicht zu unterstützen , synchronizedauf defaultMethoden.
Lukas Eder

3
@LukasEder Ich sehe, das gleiche hier. Wer hätte das erwarten können? Die Gründe sind ziemlich überzeugend, insbesondere für diese finalFrage, und es ist etwas demütig, dass niemand an ähnliche Beispiele zu denken schien (oder vielleicht einige über diese Beispiele nachdachten, sich aber nicht autoritativ genug fühlten, um zu antworten). Nun wird das letzte Wort gesprochen (Entschuldigung, ich muss das tun :).
Marco13

4

Ich würde nicht denken, dass es notwendig ist, finaleine Convenience-Interface-Methode anzugeben. Ich kann zwar zustimmen, dass dies hilfreich sein mag , aber anscheinend haben die Kosten die Vorteile übergewichtet .

Was Sie in beiden Fällen tun sollen, ist, das richtige Javadoc für die Standardmethode zu schreiben und genau zu zeigen, was die Methode ist und was nicht. Auf diese Weise dürfen die Klassen, die die Schnittstelle implementieren, die Implementierung nicht ändern, obwohl es keine Garantien gibt.

Jeder kann eine schreiben Collection, die an der Benutzeroberfläche haftet und dann Dinge in Methoden tut, die absolut kontraintuitiv sind. Es gibt keine Möglichkeit, sich davor zu schützen, außer umfangreiche Unit-Tests zu schreiben.


2
Javadoc-Verträge sind eine gültige Problemumgehung für das konkrete Beispiel, das ich in meiner Frage aufgeführt habe, aber es geht wirklich nicht um den Anwendungsfall der Convenience-Interface-Methode. Die Frage bezieht sich auf einen maßgeblichen Grund, warum finalentschieden wurde, Java 8- interfaceMethoden nicht zuzulassen. Ein unzureichendes Kosten-Nutzen-Verhältnis ist ein guter Kandidat, aber bisher ist das Spekulation.
Lukas Eder

0

Wir fügen defaultunserer Methode ein Schlüsselwort hinzu, interfacewenn wir wissen, dass die Klasse die Implementierung erweitert interfaceoder nicht override. Was aber, wenn wir eine Methode hinzufügen möchten, die von keiner implementierenden Klasse überschrieben werden soll? Nun, wir hatten zwei Möglichkeiten:

  1. Fügen Sie eine default finalMethode hinzu.
  2. Fügen Sie eine staticMethode hinzu.

Java sagt nun, dass wir eine classImplementierung dieser Methode in unserer Klasse bereitstellen müssen , wenn wir zwei oder mehr implementieren, interfacessodass sie eine defaultMethode mit genau demselben Methodennamen und derselben Signatur haben, dh sie sind doppelt vorhanden. Im Falle von default finalMethoden können wir keine Implementierung bereitstellen und stecken fest. Und deshalb wird das finalSchlüsselwort in Schnittstellen nicht verwendet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.