Was ist der Sinn der "letzten Klasse" in Java?


569

Ich lese ein Buch über Java und es heißt, dass Sie die gesamte Klasse als deklarieren können final. Ich kann mir nichts vorstellen, wo ich das verwenden würde.

Ich bin neu in der Programmierung und frage mich, ob Programmierer dies tatsächlich in ihren Programmen verwenden . Wenn ja, wann verwenden sie es, damit ich es besser verstehen und wissen kann, wann ich es verwenden soll.

Wenn Java objektorientiert ist und Sie eine Klasse deklarieren final, stoppt es dann nicht die Idee, dass eine Klasse die Eigenschaften von Objekten hat?

Antworten:


531

Zunächst empfehle ich diesen Artikel: Java: Wann wird eine endgültige Klasse erstellt?


Wenn ja, wann verwenden sie es, damit ich es besser verstehen und wissen kann, wann ich es verwenden soll.

Eine finalKlasse ist einfach eine Klasse, die nicht erweitert werden kann .

(Dies bedeutet nicht, dass alle Verweise auf Objekte der Klasse so tun würden, als ob sie als deklariert wären final.)

Wenn es nützlich ist, eine Klasse als endgültig zu deklarieren, wird in den Antworten auf diese Frage Folgendes behandelt:

Wenn Java objektorientiert ist und Sie eine Klasse deklarieren final, stoppt es dann nicht die Idee, dass eine Klasse die Eigenschaften von Objekten hat?

In gewissem Sinne ja.

Indem Sie eine Klasse als endgültig markieren, deaktivieren Sie eine leistungsstarke und flexible Funktion der Sprache für diesen Teil des Codes. Einige Klassen sollten jedoch nicht (und können in bestimmten Fällen auch nicht) so gestaltet werden, dass Unterklassen auf gute Weise berücksichtigt werden. In diesen Fällen ist es sinnvoll, die Klasse als endgültig zu markieren, obwohl dies die OOP einschränkt. (Denken Sie jedoch daran, dass eine Abschlussklasse noch eine andere Nicht-Abschlussklasse erweitern kann.)


39
Um die Antwort zu ergänzen, besteht eines der Prinzipien von Effective Java darin, die Komposition der Vererbung vorzuziehen. Die Verwendung des letzten Schlüsselworts hilft auch, dieses Prinzip durchzusetzen.
Riggy

9
"Sie tun dies hauptsächlich aus Effizienz- und Sicherheitsgründen." Ich höre diese Bemerkung ziemlich oft (sogar Wikipedia gibt dies an), aber ich verstehe die Gründe für dieses Argument immer noch nicht. Möchte jemand erklären, wie beispielsweise ein nicht endgültiger java.lang.String entweder ineffizient oder unsicher geworden wäre?
MRA

27
@MRA Wenn ich eine Methode erstelle, die einen String als Parameter akzeptiert, gehe ich davon aus, dass er unveränderlich ist, da es sich um Strings handelt. Infolgedessen weiß ich, dass ich jede Methode für das String-Objekt sicher aufrufen und den übergebenen String nicht ändern kann. Wenn ich String erweitern und die Implementierung von Teilzeichenfolgen ändern würde, um den tatsächlichen String zu ändern, ist das String-Objekt, von dem Sie erwartet haben, dass es unveränderlich ist, nicht mehr unveränderlich.
Cruncher

1
@Sortofabeginner Und sobald Sie sagen, dass alle String-Methoden und -Felder endgültig sein sollen, damit Sie eine Klasse mit zusätzlichen Funktionen erstellen können ... An diesem Punkt können Sie auch eine Klasse mit einem String und erstellen Erstellen Sie Methoden, die mit dieser Zeichenfolge arbeiten.
Cruncher

1
@Shay final wird (unter anderem) verwendet, um ein Objekt unveränderlich zu machen, daher würde ich nicht sagen, dass sie nichts miteinander zu tun haben. Siehe hier docs.oracle.com/javase/tutorial/essential/concurrency/…
Celeritas

184

In Java können Elemente mit dem finalModifikator nicht geändert werden!

Dies umfasst endgültige Klassen, endgültige Variablen und endgültige Methoden:

  • Eine Abschlussklasse kann nicht um eine andere Klasse erweitert werden
  • Einer endgültigen Variablen kann kein anderer Wert zugewiesen werden
  • Eine endgültige Methode kann nicht überschrieben werden

40
Die eigentliche Frage ist warum , nicht was .
Francesco Menzani

8
Die Aussage "In Java können Elemente mit dem finalModifikator nicht geändert werden!" Ist zu kategorisch und in der Tat nicht ganz korrekt. Wie Grady Booch es ausdrückte: "Ein Objekt hat Zustand, Verhalten und Identität". Obwohl wir die Identität eines Objekts nicht ändern können, sobald seine Referenz als endgültig markiert wurde, haben wir die Möglichkeit, seinen Status zu ändern, indem wir seinen Nichtfeldern neue Werte zuweisen final(vorausgesetzt natürlich, es hat sie.) Jeder, der es ist Wenn Sie planen, eine Oracle Java-Zertifizierung (wie 1Z0-808 usw.) zu erhalten, sollten Sie dies berücksichtigen, da möglicherweise Fragen zu diesem Aspekt in der Prüfung enthalten sind ...
Igor Soudakevitch,

33

Ein Szenario, in dem final aus Sicherheitsgründen wichtig ist, wenn Sie die Vererbung einer Klasse verhindern möchten . Auf diese Weise können Sie sicherstellen, dass der von Ihnen ausgeführte Code nicht von jemandem überschrieben werden kann.

Ein anderes Szenario dient der Optimierung: Ich erinnere mich anscheinend, dass der Java-Compiler einige Funktionsaufrufe von endgültigen Klassen einfügt. Wenn Sie also aufrufen a.x()und a deklariert ist final, wissen wir zur Kompilierungszeit, wie der Code aussehen wird, und können ihn in die aufrufende Funktion integrieren. Ich habe keine Ahnung, ob dies tatsächlich getan wird, aber mit final ist es eine Möglichkeit.


7
Das Inlining wird normalerweise nur vom Just-in-Time-Compiler zur Laufzeit ausgeführt. Es funktioniert auch ohne final, aber der JIT-Compiler muss etwas mehr tun, um sicherzugehen, dass es keine erweiterten Klassen gibt (oder dass diese erweiterten Klassen diese Methode nicht berühren).
Paŭlo Ebermann

Eine gute Zusammenfassung zum Thema Inlining und Optimierung finden Sie hier: lemire.me/blog/archives/2014/12/17/…
Josh Hemann

24

Das beste Beispiel ist

öffentliche letzte Klasse String

Das ist eine unveränderliche Klasse und kann nicht erweitert werden. Natürlich gibt es mehr als nur das Klassenfinale unveränderlich zu machen.


Hehe, manchmal schützt es Rube Goldergian Entwickler vor sich selbst.
Zoidberg

16

Relevante Lektüre: Das Open-Closed-Prinzip von Bob Martin.

Schlüsselzitat:

Software-Entitäten (Klassen, Module, Funktionen usw.) sollten für Erweiterungen geöffnet, für Änderungen jedoch geschlossen sein.

Das finalSchlüsselwort ist das Mittel, um dies in Java zu erzwingen, unabhängig davon, ob es für Methoden oder für Klassen verwendet wird.


6
@ Sean: Wird finaldie Klasse nicht deklariert, um sie zu erweitern, anstatt sie zu öffnen? Oder nehme ich es zu wörtlich?
Goran Jovic

4
@Goran weltweit endgültig bewerben, ja. Der Schlüssel ist, das Finale selektiv an Stellen anzuwenden, an denen Sie keine Änderungen wünschen (und natürlich gute Haken für die Erweiterung bereitzustellen)
Sean Patrick Floyd

26
In OCP bezieht sich "Änderung" auf das Ändern des Quellcodes und "Erweiterung" auf die Vererbung der Implementierung. Daher ist die Verwendung finaleiner Klassen- / Methodendeklaration nicht sinnvoll, wenn der Implementierungscode zur Änderung geschlossen, aber zur Erweiterung durch Vererbung geöffnet werden soll.
Rogério

1
@ Rogerio Ich habe die Referenz (und die Interpretation) aus der Spring Framework Reference (MVC) ausgeliehen . IMHO ist dies viel sinnvoller als die Originalversion.
Sean Patrick Floyd

Verlängerung ist tot. Nutzlos. Dezimiert. Zerstört. OCP ist mir egal. Es gibt nie eine Entschuldigung, eine Klasse zu erweitern.
Josh Woodcock

15

Wenn Sie sich die Klassenhierarchie als Baum vorstellen (wie in Java), können abstrakte Klassen nur Zweige sein, und endgültige Klassen können nur Blätter sein. Klassen, die in keine dieser Kategorien fallen, können sowohl Zweige als auch Blätter sein.

Es gibt hier keine Verletzung der OO-Prinzipien, final bietet einfach eine schöne Symmetrie.

In der Praxis möchten Sie final verwenden, wenn Ihre Objekte unveränderlich sein sollen oder wenn Sie eine API schreiben, um den Benutzern der API zu signalisieren, dass die Klasse nur nicht für die Erweiterung vorgesehen ist.


13

Das Schlüsselwort finalselbst bedeutet, dass etwas endgültig ist und in keiner Weise geändert werden soll. Wenn eine Klasse markiert finalist, kann sie nicht erweitert oder unterklassifiziert werden. Aber die Frage ist, warum wir eine Klasse markieren final? IMO gibt es verschiedene Gründe:

  1. Standardisierung: Einige Klassen führen Standardfunktionen aus und dürfen nicht geändert werden, z. B. Klassen, die verschiedene Funktionen im Zusammenhang mit Zeichenfolgenmanipulationen oder mathematischen Funktionen usw. ausführen.
  2. Sicherheitsgründe : Manchmal schreiben wir Klassen, die verschiedene Authentifizierungs- und Kennwortfunktionen ausführen, und wir möchten nicht, dass sie von anderen geändert werden.

Ich habe gehört, dass die Markierungsklasse finaldie Effizienz verbessert, aber ehrlich gesagt konnte ich nicht feststellen, dass dieses Argument viel Gewicht hat.

Wenn Java objektorientiert ist und Sie ein Klassenfinale deklarieren, stoppt es dann nicht die Idee, dass eine Klasse die Eigenschaften von Objekten hat?

Vielleicht ja, aber manchmal ist das der beabsichtigte Zweck. Manchmal tun wir dies, um größere Vorteile der Sicherheit usw. zu erzielen, indem wir die Fähigkeit dieser Klasse opfern, erweitert zu werden. Eine Abschlussklasse kann jedoch bei Bedarf noch eine Klasse erweitern.

Nebenbei bemerkt sollten wir die Komposition der Vererbung vorziehen, und das finalSchlüsselwort hilft tatsächlich bei der Durchsetzung dieses Prinzips.


6

Seien Sie vorsichtig, wenn Sie eine Klasse "final" machen. Wenn Sie einen Komponententest für eine Abschlussklasse schreiben möchten, können Sie diese Abschlussklasse nicht unterordnen, um die in Michael C. Feathers 'Buch "Effektiv mit Legacy-Code arbeiten" beschriebene Technik zum Unterbrechen von Abhängigkeiten "Unterklassen- und Überschreibungsmethode" zu verwenden. . In diesem Buch sagte Feathers: "Im Ernst, es ist leicht zu glauben, dass versiegelt und endgültig ein falscher Fehler sind, dass sie niemals zu Programmiersprachen hinzugefügt werden sollten. Aber der wahre Fehler liegt bei uns. Wenn wir direkt davon abhängen." Bibliotheken, die außerhalb unserer Kontrolle liegen, bitten wir nur um Ärger. "


6

final class kann vermeiden, dass die öffentliche API beschädigt wird, wenn Sie neue Methoden hinzufügen

Angenommen, BaseSie tun in Version 1 Ihrer Klasse Folgendes:

public class Base {}

und ein Kunde tut:

class Derived extends Base {
    public int method() { return 1; }
}

Wenn Sie dann in Version 2 eine methodMethode hinzufügen möchten zu Base:

class Base {
    public String method() { return null; }
}

es würde den Client-Code brechen.

Wenn wir final class Basestattdessen verwendet hätten, hätte der Client nicht erben können, und das Hinzufügen von Methoden würde die API nicht beschädigen.


5

Wenn die Klasse markiert ist final, bedeutet dies, dass die Struktur der Klasse durch nichts Externes geändert werden kann. Dies ist am sichtbarsten, wenn Sie eine traditionelle polymorphe Vererbung durchführen, die im Grunde class B extends Aeinfach nicht funktioniert. Dies ist im Grunde eine Möglichkeit, einige Teile Ihres Codes zu schützen (soweit) .

Zur Verdeutlichung markiert finaldas Markieren einer Klasse ihre Felder nicht finalals solche und schützt daher nicht die Objekteigenschaften, sondern die tatsächliche Klassenstruktur.


1
Was bedeuten die Objekteigenschaften? Bedeutet das, dass ich die Mitgliedsvariable der Klasse ändern könnte, wenn die Klasse als endgültig deklariert wird? Der einzige Zweck der letzten Klasse besteht also darin, die Vererbung zu verhindern.
Adam Lyu

5

So adressieren Sie das endgültige Klassenproblem:

Es gibt zwei Möglichkeiten, ein Klassenfinale zu erreichen. Das erste besteht darin, das Schlüsselwort final in der Klassendeklaration zu verwenden:

public final class SomeClass {
  //  . . . Class contents
}

Die zweite Möglichkeit, ein Klassenfinale zu erreichen, besteht darin, alle Konstruktoren als privat zu deklarieren:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

Wenn Sie es als endgültig markieren, sparen Sie sich die Mühe, wenn Sie feststellen, dass es sich tatsächlich um ein Finale handelt, um den Blick auf diese Testklasse zu demonstrieren. sieht auf den ersten Blick öffentlich aus.

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

Da der einzige Konstruktor der Klasse privat ist, ist es leider unmöglich, diese Klasse zu erweitern. Im Fall der Testklasse gibt es keinen Grund, warum die Klasse endgültig sein sollte. Die Testklasse ist ein gutes Beispiel dafür, wie implizite Abschlussklassen Probleme verursachen können.

Sie sollten es also als endgültig markieren, wenn Sie eine Klasse implizit endgültig machen, indem Sie ihren Konstruktor privat machen.


4

Eine Abschlussklasse ist eine Klasse, die nicht erweitert werden kann. Außerdem könnten Methoden als endgültig deklariert werden, um anzuzeigen, dass sie nicht von Unterklassen überschrieben werden können.

Das Verhindern, dass die Klasse in Unterklassen unterteilt wird, kann besonders nützlich sein, wenn Sie APIs oder Bibliotheken schreiben und eine Erweiterung vermeiden möchten, um das Basisverhalten zu ändern.


4

Ein Vorteil, eine Klasse als endgültig zu halten:

Die String-Klasse wird endgültig gehalten, damit niemand ihre Methoden überschreiben und die Funktionalität ändern kann. zB kann niemand die Funktionalität der length () -Methode ändern. Es wird immer die Länge eines Strings zurückgegeben.

Der Entwickler dieser Klasse wollte, dass niemand die Funktionalität dieser Klasse ändert, deshalb behielt er sie als endgültig bei.


3

Ja, manchmal möchten Sie dies jedoch aus Sicherheits- oder Geschwindigkeitsgründen. Es ist auch in C ++ gemacht. Es kann nicht sein , dass anwendbar für Programme, aber um so mehr für Gerüste. http://www.glenmccl.com/perfj_025.htm


3

In Java wird das endgültige Schlüsselwort für die folgenden Gelegenheiten verwendet.

  1. Letzte Variablen
  2. Endgültige Methoden
  3. Abschlussklassen

In Java können endgültige Variablen nicht neu zugewiesen werden, endgültige Klassen können nicht erweitert werden und endgültige Methoden können nicht überschrieben werden.


1

Abschlussklassen können nicht verlängert werden. Wenn Sie also möchten, dass sich eine Klasse auf eine bestimmte Weise verhält und die Methoden nicht von jemandem überschrieben werden (mit möglicherweise weniger effizientem und schädlicherem Code), können Sie die gesamte Klasse als endgültige oder spezifische Methode deklarieren, die Sie nicht sein möchten geändert.

Da das Deklarieren einer Klasse nicht verhindert, dass eine Klasse instanziiert wird, bedeutet dies nicht, dass die Klasse nicht die Eigenschaften eines Objekts aufweist. Es ist nur so, dass Sie sich an die Methoden halten müssen, wie sie in der Klasse deklariert sind.


1

Stellen Sie sich FINAL als das "Ende der Reihe" vor - dieser Typ kann keine Nachkommen mehr hervorbringen. Wenn Sie es so sehen, gibt es unzählige reale Szenarien, auf die Sie stoßen müssen, bei denen Sie der Klasse einen Zeilenende-Marker markieren müssen. Es handelt sich um domänengesteuertes Design. Wenn Ihre Domain verlangt, dass eine bestimmte ENTITY (Klasse) keine Unterklassen erstellen kann, markieren Sie sie als FINAL.

Ich sollte beachten, dass Sie nichts daran hindert, eine Klasse zu erben, die als endgültig gekennzeichnet werden sollte. Dies wird jedoch im Allgemeinen als "Missbrauch der Vererbung" klassifiziert und erfolgt, weil Sie meistens eine Funktion von der Basisklasse in Ihrer Klasse erben möchten.

Der beste Ansatz besteht darin, sich die Domäne anzusehen und sie Ihre Entwurfsentscheidungen bestimmen zu lassen.


1

Wie oben erwähnt, können Sie die Funktionalität der Methode als endgültig deklarieren, wenn Sie möchten, dass niemand die Funktionalität der Methode ändern kann.

Beispiel: Anwendungsserver-Dateipfad zum Herunterladen / Hochladen, Aufteilen der Zeichenfolge basierend auf dem Offset. Solche Methoden können Sie als endgültig deklarieren, damit diese Methodenfunktionen nicht geändert werden. Wenn Sie solche endgültigen Methoden in einer separaten Klasse möchten, definieren Sie diese Klasse als endgültige Klasse. Die letzte Klasse verfügt also über alle endgültigen Methoden, wobei die endgültige Methode in der nicht endgültigen Klasse deklariert und definiert werden kann.



1

Angenommen, Sie haben eine EmployeeKlasse mit einer Methode greet. Wenn die greetMethode aufgerufen wird, wird sie einfach gedruckt Hello everyone!. Das ist also das erwartete Verhalten der greetMethode

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Lassen Sie nun die GrumpyEmployeeUnterklasse Employeeund überschreiben Sie die greetMethode wie unten gezeigt.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Schauen Sie sich jetzt im folgenden Code die an sayHello Methode an. Es nimmt die EmployeeInstanz als Parameter und ruft die greet-Methode auf, in der Hoffnung, dass sie sagen würde, Hello everyone!aber was wir bekommen, ist Get lost!. Diese Verhaltensänderung ist auf zurückzuführenEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Diese Situation kann vermieden werden, wenn dieEmployee Klasse gemacht wurde final. Stellen Sie sich vor, wie viel Chaos ein frecher Programmierer verursachen könnte, wenn StringClass nicht als deklariert würde final.


1

Die Abschlussklasse kann nicht weiter erweitert werden. Wenn wir eine Klasse in Java nicht vererbbar machen müssen, können wir diesen Ansatz verwenden.

Wenn wir nur bestimmte Methoden in einer Klasse festlegen müssen, um nicht überschrieben zu werden, können wir ihnen nur das letzte Schlüsselwort vorlegen. Dort ist die Klasse noch vererbbar.


-1

Bei der Objektorientierung geht es nicht um Vererbung, sondern um Kapselung. Und die Vererbung unterbricht die Kapselung.

In vielen Fällen ist es durchaus sinnvoll, ein Klassenfinale zu erklären. Jedes Objekt, das einen „Wert“ wie eine Farbe oder einen Geldbetrag darstellt, kann endgültig sein. Sie stehen alleine da.

Wenn Sie Bibliotheken schreiben, machen Sie Ihre Klassen endgültig, es sei denn, Sie rücken sie ausdrücklich ein, um sie abzuleiten. Andernfalls können Personen Ihre Klassen ableiten und Methoden überschreiben, wodurch Ihre Annahmen / Invarianten verletzt werden. Dies kann auch Auswirkungen auf die Sicherheit haben.

Joshua Bloch in „Effective Java“ empfiehlt, explizit für die Vererbung zu entwerfen oder zu verbieten, und er stellt fest, dass das Entwerfen für die Vererbung nicht so einfach ist.

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.