web-dev-qa-db-de.com

Warum sollte man eine unveränderliche Klasse in Java als final deklarieren?

Ich habe das gelesen, um eine Klasse unveränderlich in Java zu machen, sollten wir folgendes tun, 

  1. Stellen Sie keine Setter bereit 
  2. Alle Felder als privat markieren 
  3. Machen Sie die Klasse endgültig

Warum ist Schritt 3 erforderlich? Warum sollte ich die Klasse final markieren? 

66
Anand

Wenn Sie die Klasse nicht als final markieren, kann ich möglicherweise Ihre scheinbar unveränderliche Klasse plötzlich veränderlich machen. Betrachten Sie zum Beispiel diesen Code:

public class Immutable {
     private final int value;

     public Immutable(int value) {
         this.value = value;
     }

     public int getValue() {
         return value;
     }
}

Nehmen wir an, ich mache folgendes:

public class Mutable extends Immutable {
     private int realValue;

     public Mutable(int value) {
         super(value);

         realValue = value;
     }

     public int getValue() {
         return realValue;
     }
     public void setValue(int newValue) {
         realValue = newValue;
     }

    public static void main(String[] arg){
        Mutable obj = new Mutable(4);
        Immutable immObj = (Immutable)obj;              
        System.out.println(immObj.getValue());
        obj.setValue(8);
        System.out.println(immObj.getValue());
    }
}

Beachten Sie, dass ich in meiner Mutable-Unterklasse das Verhalten von getValue überschrieben habe, um ein neues, in meiner Unterklasse deklariertes veränderbares Feld zu lesen. Daher ist Ihre Klasse, die anfangs unveränderlich wirkt, wirklich nicht unveränderlich. Ich kann dieses Mutable-Objekt überall dort übergeben, wo ein Immutable-Objekt erwartet wird, was sehr schlechte Dinge für den Code bedeuten kann, vorausgesetzt, das Objekt ist wirklich unveränderlich. Durch die Markierung der Basisklasse final wird dies verhindert.

Hoffe das hilft!

110
templatetypedef

Im Gegensatz zu dem, was viele Leute glauben, ist das Erstellen einer unveränderlichen Klasse finalnicht erforderlich.

Das Standardargument für unveränderliche Klassen final lautet: Wenn Sie dies nicht tun, können Unterklassen Mutabilität hinzufügen, wodurch der Vertrag der Oberklasse verletzt wird. Kunden der Klasse gehen von Unveränderlichkeit aus, werden aber überrascht sein, wenn sich unter ihnen etwas verändert.

Wenn Sie dieses Argument auf das logische Extrem bringen, sollten all Methoden zu final gemacht werden, da andernfalls eine Unterklasse eine Methode auf eine Weise überschreiben könnte, die nicht dem Vertrag ihrer Oberklasse entspricht. Es ist interessant, dass die meisten Java-Programmierer dies als lächerlich empfinden, aber irgendwie einig sind, dass unveränderliche Klassen final sein sollten. Ich vermute, dass es etwas damit zu tun hat, dass Java-Programmierer im Allgemeinen mit dem Begriff der Unveränderlichkeit nicht ganz vertraut sind, und vielleicht eine Art unscharfes Denken in Bezug auf die verschiedenen Bedeutungen des final-Schlüsselworts in Java.

Die Einhaltung des Vertrages Ihrer Oberklasse ist nicht etwas, das vom Compiler durchgesetzt werden kann oder sollte. Der Compiler kann bestimmte Aspekte Ihres Vertrages durchsetzen (z. B. ein Mindestsatz an Methoden und deren Typensignaturen). Es gibt jedoch viele Teile typischer Verträge, die vom Compiler nicht durchgesetzt werden können.

Unveränderlichkeit ist Teil des Vertrags einer Klasse. Es ist ein wenig anders als bei einigen Dingen, an die die Leute eher gewöhnt sind, denn es sagt aus, was die Klasse (und alle Unterklassen) nicht tun können, während ich glaube, dass die meisten Java- (und allgemein OOP-) Programmierer dazu neigen Denken Sie an Verträge, die sich darauf beziehen, was eine Klasse can tut, nicht was sie (kann) nicht tut. 

Die Unveränderlichkeit wirkt sich nicht nur auf eine einzelne Methode aus, sondern auch auf die gesamte Instanz. Dies ist jedoch nicht viel anders als die Art und Weise, in der equals und hashCode in Java arbeiten. Diese beiden Methoden haben einen spezifischen Vertrag in Object. Dieser Vertrag legt sehr sorgfältig Dinge fest, die diese Methoden können nicht tun. Dieser Vertrag wird in Unterklassen genauer festgelegt. Es ist sehr leicht, equals oder hashCode vertragswidrig zu überschreiben. Wenn Sie nur eine dieser beiden Methoden ohne die andere überschreiben, besteht die Gefahr, dass Sie gegen den Vertrag verstoßen. Sollten equals und hashCode in finalObject deklariert sein, um dies zu vermeiden? Ich denke, die meisten würden argumentieren, dass sie dies nicht tun sollten. Ebenso ist es nicht notwendig, unveränderliche Klassen final zu erstellen.

Das heißt, die meisten Ihrer Klassen, unveränderlich oder nicht, wahrscheinlich solltefinal sein. Siehe Effektive Java Second Edition Element 17: "Entwerfen und Dokumentieren für Vererbung oder Verbieten". 

Eine korrekte Version Ihres Schritts 3 wäre also: "Machen Sie die Klasse final" oder dokumentieren Sie beim Entwurf für Unterklassen eindeutig, dass alle Unterklassen weiterhin unveränderlich sein müssen. "

35

Markieren Sie nicht die gesamte Klasse als final.

Es gibt triftige Gründe dafür, dass eine unveränderliche Klasse erweitert werden kann, wie in einigen anderen Antworten angegeben. Daher ist es nicht immer eine gute Idee, die Klasse als final zu kennzeichnen. 

Es ist besser, Ihre Eigenschaften als privat und endgültig zu kennzeichnen und wenn Sie den "Vertrag" schützen möchten, markieren Sie Ihre Getter als endgültig. 

Auf diese Weise können Sie zulassen, dass die Klasse erweitert wird (ja möglicherweise sogar um eine veränderliche Klasse). Die unveränderlichen Aspekte Ihrer Klasse sind jedoch geschützt. Die Eigenschaften sind privat und können nicht aufgerufen werden. Die Eigenschaften dieser Eigenschaften sind endgültig und können nicht überschrieben werden. 

Jeder andere Code, der eine Instanz Ihrer unveränderlichen Klasse verwendet, kann sich auf die unveränderlichen Aspekte Ihrer Klasse verlassen, selbst wenn die übergebene Unterklasse in anderen Aspekten veränderbar ist. Da es eine Instanz Ihrer Klasse erfordert, würde sie natürlich nicht einmal über diese anderen Aspekte Bescheid wissen.

18
Justin Ohms

Wenn dies nicht endgültig ist, kann jeder die Klasse erweitern und tun, was immer sie möchten, z. B. Setter bereitstellen, Ihre privaten Variablen spiegeln und im Grunde veränderlich machen.

5
digitaljoel

Dies beschränkt andere Klassen, die Ihre Klasse erweitern.

die letzte Klasse kann nicht durch andere Klassen erweitert werden.

Wenn eine Klasse die Klasse, die Sie als unveränderlich erstellen möchten, erweitert, ändert sich möglicherweise der Status der Klasse aufgrund von Vererbungsprinzipien.

Klären Sie einfach "es kann sich ändern". Die Unterklasse kann das Verhalten der Superklasse wie das Überschreiben von Methoden überschreiben (z. B. templatetypedef/Ted Hop-Antwort).

4
kosa

Für die Erstellung einer unveränderlichen Klasse ist es nicht zwingend, die Klasse als endgültig zu kennzeichnen.

Lassen Sie mich eines dieser Beispiele aus Java-Klassen nehmen. Die "BigInteger" -Klasse ist unveränderlich, aber nicht endgültig.

Tatsächlich ist die Unveränderlichkeit ein Konzept, nach dem das Objekt erstellt und dann nicht geändert werden kann.

Nehmen wir an, aus Sicht der JVM müssen alle Threads aus der Sicht der JVM die gleiche Kopie des Objekts verwenden, und es ist vollständig erstellt, bevor ein Thread darauf zugreift und der Status des Objekts ändert sich nach seiner Konstruktion nicht.

Unveränderlichkeit bedeutet, dass Sie den Zustand des Objekts nicht ändern können, sobald es erstellt wurde. Dies wird durch drei Daumenregeln erreicht, durch die der Compiler erkennt, dass die Klasse unveränderlich ist.

  • alle nicht privaten Felder sollten endgültig sein
  • stellen Sie sicher, dass es keine Methode in der Klasse gibt, die die Felder des Objekts entweder direkt oder indirekt ändern kann
  • alle in der Klasse definierten Objektverweise können nicht außerhalb der Klasse geändert werden

Weitere Informationen finden Sie unter der folgenden URL

http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html

2
Tarun Bedi

Wenn Sie es nicht final machen, kann ich es erweitern und nicht veränderbar machen.

public class Immutable {
  privat final int val;
  public Immutable(int val) {
    this.val = val;
  }

  public int getVal() {
    return val;
  }
}

public class FakeImmutable extends Immutable {
  privat int val2;
  public FakeImmutable(int val) {
    super(val);
  }

  public int getVal() {
    return val2;
  }

  public void setVal(int val2) {
    this.val2 = val2;
  }
}

Jetzt kann ich FakeImmutable an jede Klasse übergeben, die Immutable erwartet, und es wird sich nicht wie der erwartete Vertrag verhalten.

1
Roger Lindsjö

Angenommen, die folgende Klasse war nicht final:

public class Foo {
    private int mThing;
    public Foo(int thing) {
        mThing = thing;
    }
    public int doSomething() { /* doesn't change mThing */ }
}

Es ist anscheinend unveränderlich, da selbst Unterklassen mThing nicht ändern können. Eine Unterklasse kann jedoch veränderbar sein:

public class Bar extends Foo {
    private int mValue;
    public Bar(int thing, int value) {
        super(thing);
        mValue = value;
    }
    public int getValue() { return mValue; }
    public void setValue(int value) { mValue = value; }
}

Ein Objekt, das einer Variablen vom Typ Foo zugewiesen werden kann, ist jetzt nicht mehr garantiert mmfähig. Dies kann Probleme mit Dingen wie Hashing, Gleichheit, Parallelität usw. verursachen.

0
Ted Hopp

Die Standardbedeutung von equals () entspricht der referentiellen Gleichheit. Bei unveränderlichen Datentypen ist dies fast immer falsch. Sie müssen also die equals () -Methode überschreiben und durch Ihre eigene Implementierung ersetzen. Verknüpfung

0
Anitha B S

Design an sich hat keinen Wert. Design wird immer verwendet, um ein Ziel zu erreichen. Was ist das Ziel hier? Möchten wir die Anzahl der Überraschungen im Code reduzieren? Wollen wir Fehler vermeiden? Folgen wir blind den Regeln?

Auch das Design kostet immer etwas. Jedes Design, das den Namen verdient, bedeutet, dass Sie einen Zielkonflikt haben .

In diesem Sinne müssen Sie Antworten auf diese Fragen finden:

  1. Wie viele offensichtliche Fehler werden dadurch verhindert?
  2. Wie viele kleine Fehler wird das verhindern?
  3. Wie oft macht dies anderen Code komplexer (= fehleranfälliger)?
  4. Ist das Testen einfacher oder schwieriger?
  5. Wie gut sind die Entwickler in Ihrem Projekt? Wie viel Führung benötigen sie mit einem Vorschlaghammer?

Angenommen, Sie haben viele junge Entwickler in Ihrem Team. Sie werden alles Mögliche versuchen, nur weil sie noch keine guten Lösungen für ihre Probleme kennen. Das Finalisieren der Klasse könnte Fehler verhindern (gut), sie kann jedoch auch dazu führen, dass sie "clevere" Lösungen finden, beispielsweise das Kopieren all dieser Klassen in veränderliche Klassen im gesamten Code.

Auf der anderen Seite wird es sehr schwierig sein, eine Klasse final zu erstellen, nachdem sie überall verwendet wird. Es ist jedoch einfach, eine final-Klasse später als -final zu definieren, wenn Sie feststellen, dass Sie sie erweitern müssen.

Wenn Sie Schnittstellen ordnungsgemäß verwenden, können Sie das Problem "Ich muss diese veränderliche Komponente erstellen" vermeiden, indem Sie die Schnittstelle immer verwenden und später bei Bedarf eine veränderliche Implementierung hinzufügen.

Fazit: Für diese Antwort gibt es keine "beste" Lösung. Es hängt davon ab, zu welchem ​​Preis Sie bereit sind und welchen Preis Sie zahlen müssen.

0
Aaron Digulla