Gute Gründe, die Vererbung in Java zu verbieten?

177

Was sind gute Gründe, um die Vererbung in Java zu verbieten, beispielsweise durch die Verwendung von Abschlussklassen oder Klassen mit einem einzigen privaten, parameterlosen Konstruktor? Was sind gute Gründe, um eine Methode endgültig zu machen?

Cretzel
quelle
1
Pedanterie: Der Standardkonstruktor wird vom Java-Compiler für Sie generiert, wenn Sie keinen explizit in Ihren Code schreiben. Du meinst den Konstruktor ohne Argument.
John Topley
Ist das nicht der "implizite Standardkonstruktor"?
Cretzel
2
"Sie müssen keine Konstruktoren für Ihre Klasse angeben, aber Sie müssen dabei vorsichtig sein. Der Compiler stellt automatisch einen Standardkonstruktor ohne Argumente für jede Klasse ohne Konstruktoren bereit." java.sun.com/docs/books/tutorial/java/javaOO/constructors.html - John Topley
John Topley

Antworten:

183

Ihre beste Referenz hier ist Punkt 19 von Joshua Blochs ausgezeichnetem Buch "Effective Java" mit dem Titel "Design und Dokument zur Vererbung oder zum Verbot". (Es ist Punkt 17 in der zweiten Ausgabe und Punkt 15 in der ersten Ausgabe.) Sie sollten es wirklich lesen, aber ich werde es zusammenfassen.

Die Interaktion geerbter Klassen mit ihren Eltern kann überraschend und unvorhersehbar sein, wenn der Vorfahr nicht für die Vererbung konzipiert wurde.

Klassen sollten daher in zwei Arten kommen:

  1. Klassen , die erweitert werden sollen und über genügend Dokumentation verfügen, um zu beschreiben, wie dies durchgeführt werden soll

  2. Klassen als endgültig markiert

Wenn Sie rein internen Code schreiben, kann dies ein wenig übertrieben sein. Der zusätzliche Aufwand beim Hinzufügen von fünf Zeichen zu einer Klassendatei ist jedoch sehr gering. Wenn Sie nur für den internen Verbrauch schreiben, kann ein zukünftiger Codierer immer das 'Finale' entfernen - Sie können es sich als Warnung vorstellen, dass "diese Klasse nicht unter Berücksichtigung der Vererbung entworfen wurde".

DJClayworth
quelle
6
Nach meiner Erfahrung ist dies kein Overkill, wenn jemand außer mir den Code verwendet. Ich habe nie verstanden, warum Java Methoden hat, die standardmäßig überschrieben werden können.
Eric Weilnau
9
Um einige von Effective Java zu verstehen, müssen Sie Joshs Design verstehen. Er sagt, Sie sollten Klassenschnittstellen immer so gestalten, als wären sie eine öffentliche API. Ich denke, die Mehrheit ist der Meinung, dass dieser Ansatz normalerweise zu schwer und unflexibel ist. (YAGNI usw.)
Tom Hawtin - Tackline
9
Gute Antwort. (Ich kann nicht widerstehen darauf hinzuweisen, dass es sechs Zeichen sind, da es auch ein Leerzeichen gibt ... ;-)
joel.neely
36
Bitte beachten Sie, dass das Abschluss des Unterrichts das Testen erschweren kann (schwieriger zu
stummeln
10
Wenn die letzte Klasse auf eine Schnittstelle schreibt, sollte das Verspotten kein Problem sein.
Fred Haslam
26

Möglicherweise möchten Sie eine Methode endgültig festlegen, damit überschreibende Klassen das Verhalten nicht ändern können, auf das in anderen Methoden gerechnet wird. In Konstruktoren aufgerufene Methoden werden häufig als endgültig deklariert, damit Sie beim Erstellen von Objekten keine unangenehmen Überraschungen erleben.

Bill die Eidechse
quelle
Ich habe diese Antwort nicht ganz verstanden. Wenn es Ihnen nichts ausmacht, können Sie erklären, was Sie unter "Überschreiben von Klassen können das Verhalten, auf das bei anderen Methoden zählen, nicht ändern können" verstehen? Ich frage erstens, weil ich den Satz nicht verstanden habe, und zweitens, weil Netbeans empfiehlt, dass eine meiner Funktionen, die ich vom Konstruktor aufrufe, sein sollte final.
Nav
1
@Nav Wenn Sie eine Methode endgültig machen, kann eine überschreibende Klasse keine eigene Version dieser Methode haben (oder es handelt sich um einen Kompilierungsfehler). Auf diese Weise wissen Sie, dass sich das Verhalten, das Sie in dieser Methode implementieren, später nicht ändert, wenn jemand anderes die Klasse erweitert.
Bill the Lizard
19

Ein Grund für ein Klassenfinale wäre, wenn Sie die Komposition der Vererbung aufzwingen möchten. Dies ist im Allgemeinen wünschenswert, um eine enge Kopplung zwischen Klassen zu vermeiden.

John Topley
quelle
15

Es gibt 3 Anwendungsfälle, in denen Sie endgültige Methoden anwenden können.

  1. Um zu vermeiden, dass abgeleitete Klassen eine bestimmte Basisklassenfunktionalität überschreiben.
  2. Dies dient Sicherheitszwecken, bei denen die Basisklasse einige wichtige Kernfunktionen des Frameworks bereitstellt, bei denen die abgeleitete Klasse diese nicht ändern soll.
  3. Endgültige Methoden sind schneller als Instanzmethoden, da das Konzept der virtuellen Tabelle für endgültige und private Methoden nicht verwendet wird. Versuchen Sie also, wo immer es eine Möglichkeit gibt, endgültige Methoden anzuwenden.

Zweck für ein Klassenfinale:

Damit kein Körper diese Klassen erweitern und ihr Verhalten ändern kann.

Beispiel: Wrapper-Klasse Integer ist eine letzte Klasse. Wenn diese Klasse nicht endgültig ist, kann jeder Integer in seine eigene Klasse erweitern und das Grundverhalten der Integer-Klasse ändern. Um dies zu vermeiden, hat Java alle Wrapper-Klassen als endgültige Klassen erstellt.

user1923551
quelle
Nicht sehr überzeugt von Ihrem Beispiel. Wenn dies der Fall ist, warum nicht die Methoden und Variablen endgültig machen, anstatt die gesamte Klasse endgültig zu machen? Können Sie bitte Ihre Antwort mit den Details bearbeiten.
Rajkiran
4
Selbst wenn Sie alle Variablen und Methoden als endgültig festlegen, können andere Ihre Klasse erben, zusätzliche Funktionen hinzufügen und das grundlegende Verhalten Ihrer Klasse ändern.
user1923551
3
> Wenn diese Klasse nicht endgültig ist, kann jeder Integer in seine eigene Klasse erweitern und das Grundverhalten der Integer-Klasse ändern. Nehmen wir an, dies war erlaubt und diese neue Klasse wurde aufgerufen. DerivedIntegerDie ursprüngliche Integer-Klasse wird immer noch nicht geändert. Wer sie verwendet, DerivedIntegertut dies auf eigenes Risiko. Daher verstehe ich immer noch nicht, warum dies ein Problem ist.
Siddhartha
7

Vererbung ist wie eine Kettensäge - sehr mächtig, aber schrecklich in den falschen Händen. Entweder entwerfen Sie eine Klasse, von der geerbt werden soll (was die Flexibilität einschränken und viel länger dauern kann), oder Sie sollten sie verbieten.

Siehe Effective Java 2nd Edition, Artikel 16 und 17, oder meinen Blog-Beitrag "Inheritance Tax" .

Jon Skeet
quelle
Der Link zum Blog-Beitrag ist defekt. Denkst du auch, du könntest etwas mehr darauf eingehen?
@ MichaelT: Der Link wurde korrigiert, danke - folge ihm für weitere Ausarbeitungen :) (Ich fürchte, ich habe momentan keine Zeit, dies zu ergänzen.)
Jon Skeet
@ JonSkeet, Der Link zum Blog-Beitrag ist defekt.
Luzifer
@Kedarnath: Es funktioniert für mich ... es war für eine Weile kaputt, aber es zeigt jetzt auf die richtige Seite auf codeblog.jonskeet.uk ...
Jon Skeet
4

Hmmm ... ich kann mir zwei Dinge vorstellen:

Möglicherweise haben Sie eine Klasse, die sich mit bestimmten Sicherheitsproblemen befasst. Ein Angreifer kann Sicherheitsbeschränkungen umgehen, indem er es unterordnet und Ihrem System die untergeordnete Version davon zuführt. Beispielsweise unterstützt Ihre Anwendung möglicherweise Plugins, und wenn ein Plugin nur Ihre sicherheitsrelevanten Klassen unterordnen kann, kann es diesen Trick verwenden, um eine untergeordnete Version davon irgendwie an seinen Platz zu schmuggeln. Dies ist jedoch eher etwas, mit dem sich Sun in Bezug auf Applets und dergleichen befassen muss, möglicherweise kein so realistischer Fall.

Viel realistischer ist es, zu vermeiden, dass ein Objekt veränderlich wird. Da Strings beispielsweise unveränderlich sind, kann Ihr Code sicher Verweise darauf behalten

 String blah = someOtherString;

anstatt zuerst die Zeichenfolge zu kopieren. Wenn Sie jedoch eine Zeichenfolge unterordnen können, können Sie Methoden hinzufügen, mit denen der Zeichenfolgenwert geändert werden kann. Jetzt kann sich kein Code mehr darauf verlassen, dass die Zeichenfolge dieselbe bleibt, wenn sie nur die Zeichenfolge wie oben kopiert, sondern die Zeichenfolge dupliziert Zeichenfolge.

Mecki
quelle
2

Wenn Sie eine kommerzielle Closed-Source-Klasse schreiben, möchten Sie möglicherweise nicht, dass die Benutzer die Funktionalität später ändern können, insbesondere wenn Sie Unterstützung dafür benötigen und die Benutzer Ihre Methode überschrieben haben und sich über den Aufruf beschweren unerwartete Ergebnisse.

DavidG
quelle
2

Wenn Sie Klassen und Methoden als endgültig markieren, stellen Sie möglicherweise einen kleinen Leistungsgewinn fest, da die Laufzeit nicht nach der richtigen Klassenmethode suchen muss, um sie für ein bestimmtes Objekt aufzurufen. Nicht endgültige Methoden werden als virtuell markiert, damit sie bei Bedarf ordnungsgemäß erweitert werden können. Endliche Methoden können direkt in der Klasse verknüpft oder inline kompiliert werden.

seanalltogether
quelle
1
Ich glaube, dies ist ein Mythos, da VMs der aktuellen Generation in der Lage sein sollten, nach Bedarf zu optimieren und zu deoptimieren. Ich erinnere mich, wie ich dies beim Bencharmking gesehen und als Mythos eingestuft habe.
Miguel Ping
1
Der Unterschied in der Codegenerierung ist kein Mythos, aber vielleicht sind es die Leistungssteigerungen. Wie ich bereits sagte, ist dies ein kleiner Gewinn. Auf einer Website wurde ein Leistungsgewinn von 3,5% erwähnt, der zum Teil höher ist. In den meisten Fällen lohnt es sich nicht, alle Nazis auf Ihren Code zu setzen.
Seanalltogether
1
Es ist kein Mythos. Tatsächlich kann der Compiler nur V-Endmethoden inline. Ich bin nicht sicher, ob JITs "Inline" -Dinge zur Laufzeit ausführen, aber ich bezweifle, dass dies möglich ist, da Sie möglicherweise später eine abgeleitete Klasse laden.
Uri
1
Microbenchmark == Salzkorn. Das heißt, nach dem Aufwärmen des 10B-Zyklus zeigen durchschnittliche Anrufe über 10B unter Eclipse auf meinem Laptop im Wesentlichen keinen Unterschied. Zwei Methoden, die String zurückgeben, final war 6.9643us, non-final war 6.9641us. Dieses Delta ist IMHO Hintergrundgeräusch.
joel.neely
1

Menschen davon abzuhalten, Dinge zu tun, die sich und andere verwirren könnten. Stellen Sie sich eine Physikbibliothek vor, in der Sie einige definierte Konstanten oder Berechnungen haben. Ohne das letzte Schlüsselwort könnte jemand mitkommen und grundlegende Berechnungen oder Konstanten neu definieren, die sich NIEMALS ändern sollten.

Anson Smith
quelle
2
Es gibt etwas, das ich an diesem Argument nicht verstehe - wenn jemand diese Berechnungen / Konstanten geändert hat, die sich nicht ändern sollten - sind keine Fehler aufgrund dieser 100% igen Schuld? Mit anderen Worten, wie kommt die Änderung etwas zugute?
Matt B
Ja, es ist technisch "ihre" Schuld, aber stellen Sie sich vor, jemand anderes, der die Bibliothek nutzt und nicht auf die Änderungen aufmerksam gemacht wurde, könnte zu Verwirrung führen. Ich habe immer gedacht, dass dies der Grund war, warum viele der Java Base-Klassen als endgültig markiert wurden, um zu verhindern, dass Benutzer grundlegende Funktionen ändern.
Anson Smith
@matt b - Sie scheinen davon auszugehen, dass der Entwickler, der die Änderung vorgenommen hat, dies wissentlich getan hat oder dass es ihn interessiert, dass es ihre Schuld ist. Wenn jemand außer mir meinen Code verwendet, werde ich ihn als endgültig markieren, es sei denn, er soll geändert werden.
Eric Weilnau
Dies ist eigentlich eine andere Verwendung von 'final' als die, nach der gefragt wurde.
DJClayworth
Diese Antwort scheint die Vererbung und Veränderbarkeit einzelner Felder mit der Fähigkeit zu verwechseln, eine Unterklasse zu schreiben. Sie können einzelne Felder und Methoden als endgültig markieren (um ihre Neudefinition zu verhindern), ohne die Vererbung zu verbieten.
joel.neely
0

Sie möchten eine Methode endgültig machen, damit das Überschreiben von Klassen ihr Verhalten nicht ändert. Wenn Sie das Verhalten ändern möchten, machen Sie die Methode öffentlich. Wenn Sie eine öffentliche Methode überschreiben, kann sie geändert werden.

Alexander Robinson
quelle