Ich habe gelesen, dass wir Folgendes tun sollten , um eine Klasse in Java unveränderlich zu machen :
- Stellen Sie keine Setter zur Verfügung
- Markieren Sie alle Felder als privat
- Mach die Klasse endgültig
Warum ist Schritt 3 erforderlich? Warum sollte ich die Klasse markieren final
?
java
immutability
final
Anand
quelle
quelle
java.math.BigInteger
Klasse ist Beispiel, seine Werte sind unveränderlich, aber es ist nicht endgültig.BigInteger
, wissen Sie nicht, ob es unveränderlich ist oder nicht. Es ist ein durcheinandergebrachtes Design. Ichjava.io.File
bin ein interessanteres Beispiel.File
ist interessant, da es wahrscheinlich als unveränderlich eingestuft wird und daher zu TOCTOU-Angriffen (Time Of Check / Time Of Use) führt. Vertrauenswürdiger Code überprüft, ob derFile
Pfad akzeptabel ist, und verwendet dann den Pfad. Eine UnterklasseFile
kann den Wert zwischen Prüfung und Verwendung ändern.Make the class final
oderAntworten:
Wenn Sie die Klasse nicht markieren
final
, kann es sein, dass ich Ihre scheinbar unveränderliche Klasse plötzlich tatsächlich veränderlich mache. Betrachten Sie beispielsweise diesen Code:public class Immutable { private final int value; public Immutable(int value) { this.value = value; } public int getValue() { return value; } }
Angenommen, 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 VerhaltengetValue
beim Lesen eines neuen, veränderlichen Felds, das in meiner Unterklasse deklariert ist , überschrieben habe . Infolgedessen ist Ihre Klasse, die zunächst unveränderlich aussieht, wirklich nicht unveränderlich. Ich kann diesesMutable
Objekt überall dort übergeben, wo einImmutable
Objekt erwartet wird, was sehr schlechte Dinge für den Code tun kann, vorausgesetzt, das Objekt ist wirklich unveränderlich. Das Markieren der Basisklassefinal
verhindert dies.Hoffe das hilft!
quelle
Immutable
ein Argument akzeptiert . Ich kannMutable
seitdem ein Objekt an diese Funktion übergebenMutable extends Immutable
. Während Sie in dieser Funktion denken, dass Ihr Objekt unveränderlich ist, könnte ich einen sekundären Thread haben, der den Wert ändert, während die Funktion ausgeführt wird. Ich könnte Ihnen auch einMutable
Objekt geben, das die Funktion speichert, und später seinen Wert extern ändern. Mit anderen Worten, wenn Ihre Funktion davon ausgeht, dass der Wert unveränderlich ist, kann er leicht beschädigt werden, da ich Ihnen ein veränderliches Objekt geben und es später ändern könnte. Ist das sinnvoll?Mutable
Objekt übergeben, ändert das Objekt nicht. Die Sorge ist, dass diese Methode annehmen könnte, dass das Objekt unveränderlich ist, wenn es wirklich nicht ist. Beispielsweise könnte die Methode davon ausgehen, dass das Objekt, da sie es für unveränderlich hält, als Schlüssel in a verwendet werden kannHashMap
. Ich könnte diese Funktion dann unterbrechen, indem ich a übergebeMutable
, darauf warte, dass es das Objekt als Schlüssel speichert, und dann dasMutable
Objekt ändere. Jetzt schlagen SuchvorgängeHashMap
fehl, da ich den dem Objekt zugeordneten Schlüssel geändert habe. Ist das sinnvoll?Im Gegensatz zu dem, was viele Leute glauben, ist
final
es nicht erforderlich , eine unveränderliche Klasse zu bilden .Das Standardargument für die Erstellung unveränderlicher Klassen
final
lautet: Wenn Sie dies nicht tun, können Unterklassen die Veränderlichkeit erhöhen und damit den Vertrag der Oberklasse verletzen. Klienten der Klasse werden Unveränderlichkeit annehmen, werden aber überrascht sein, wenn etwas unter ihnen mutiert.Wenn Sie dieses Argument auf das logische Extrem bringen, sollten alle Methoden ausgeführt werden
final
, 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 ansehen, aber irgendwie mit der Idee einverstanden sind, dass unveränderliche Klassen sein solltenfinal
. Ich vermute, dass es etwas damit zu tun hat, dass Java-Programmierer im Allgemeinen nicht ganz mit dem Begriff der Unveränderlichkeit vertraut sind und vielleicht eine Art unscharfes Denken in Bezug auf die vielfältigen Bedeutungen desfinal
Schlüsselworts in Java.Die Einhaltung des Vertrags Ihrer Oberklasse kann oder sollte vom Compiler nicht immer durchgesetzt werden. Der Compiler kann bestimmte Aspekte Ihres Vertrags erzwingen (z. B. einen Mindestsatz von Methoden und deren Typensignaturen), aber es gibt viele Teile typischer Verträge, die vom Compiler nicht erzwungen werden können.
Unveränderlichkeit ist Bestandteil des Vertrages einer Klasse. Es unterscheidet sich ein wenig von einigen Dingen, an die die Leute eher gewöhnt sind, weil es besagt, was die Klasse (und alle Unterklassen) nicht können, während ich denke, dass die meisten Java- (und im Allgemeinen OOP-) Programmierer dazu neigen, Verträge als verhältnismäßig zu betrachten Was eine Klasse kann , nicht was sie nicht kann .
Unveränderlichkeit betrifft auch mehr als nur eine einzelne Methode - sie betrifft die gesamte Instanz -, aber dies unterscheidet sich nicht wesentlich von der Art
equals
und Weise undhashCode
in der Java-Arbeit. Für diese beiden Methoden ist ein spezifischer Vertrag festgelegtObject
. Dieser Vertrag legt sehr sorgfältig Dinge fest, die diese Methoden nicht können. Dieser Vertrag wird in Unterklassen präzisiert. Es ist sehr leicht zu überschreibenequals
oderhashCode
auf eine Weise, die gegen den Vertrag verstößt. Wenn Sie nur eine dieser beiden Methoden ohne die andere überschreiben, besteht die Möglichkeit, dass Sie gegen den Vertrag verstoßen. So sollequals
undhashCode
erklärt wurde ,final
inObject
dies zu vermeiden? Ich denke, die meisten würden argumentieren, dass sie nicht sollten. Ebenso ist es nicht notwendig, unveränderliche Klassen zu erstellenfinal
.Das heißt, die meisten Ihrer Klassen, unveränderlich oder nicht, sollten es wahrscheinlich sein
final
. Siehe Effektive Java Second Edition, Punkt 17: "Entwurf und Dokument zur Vererbung oder zum Verbot".Eine korrekte Version von Schritt 3 wäre also: "Machen Sie die Klasse endgültig oder dokumentieren Sie beim Entwerfen für Unterklassen eindeutig, dass alle Unterklassen weiterhin unveränderlich sein müssen."
quelle
X
undY
der Wert vonX.equals(Y)
unveränderlich ist (solangeX
undY
weiterhin auf dieselben Objekte verwiesen wird). Es hat ähnliche Erwartungen in Bezug auf Hash-Codes. Es ist klar, dass niemand erwarten sollte, dass der Compiler die Unveränderlichkeit von Äquivalenzbeziehungen und Hash-Codes erzwingt (da dies einfach nicht möglich ist). Ich sehe keinen Grund, warum die Leute erwarten sollten, dass es für andere Aspekte eines Typs durchgesetzt wird.double
. Man könnte ein ableitenGeneralImmutableMatrix
, das ein Array als Hintergrundspeicher verwendet, aber man könnte auch z. B.ImmutableDiagonalMatrix
einfach ein Array von Elementen entlang der Diagonale speichern (das Lesen von Element X, Y würde Arr [X] ergeben, wenn X == y und sonst Null). .final
Wenn Sie immer eine unveränderliche Klasse erstellen, wird deren Nützlichkeit eingeschränkt, insbesondere wenn Sie eine API entwerfen, die erweitert werden soll. Im Interesse der Thread-Sicherheit ist es sinnvoll, Ihre Klasse so unveränderlich wie möglich zu gestalten, sie jedoch erweiterbar zu halten. Sie können die Felderprotected final
anstelle von erstellenprivate final
. Erstellen Sie dann explizit einen Vertrag (in der Dokumentation) über Unterklassen, die die Unveränderlichkeits- und Gewindesicherheitsgarantien einhalten.final
. Nur weilequals
undhashcode
nicht endgültig sein kann, heißt das nicht, dass ich dasselbe für unveränderliche Klassen tolerieren kann, es sei denn, ich habe einen gültigen Grund dafür, und in diesem Fall kann ich Dokumentation oder Tests als Verteidigungslinie verwenden.Markieren Sie nicht das gesamte Klassenfinale.
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 endgültig zu markieren.
Es ist besser, Ihre Immobilien als privat und endgültig zu markieren, 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 durch eine veränderbare Klasse), jedoch sind die unveränderlichen Aspekte Ihrer Klasse geschützt. Eigenschaften sind privat und können nicht aufgerufen werden. Getter für diese 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 sich um eine Instanz Ihrer Klasse handelt, würde sie natürlich nicht einmal über diese anderen Aspekte Bescheid wissen.
quelle
Wenn es nicht endgültig ist, kann jeder die Klasse erweitern und tun, was er will, z. B. Setter bereitstellen, Ihre privaten Variablen beschatten und sie im Grunde veränderlich machen.
quelle
Dies schränkt andere Klassen ein, die Ihre Klasse erweitern.
Die letzte Klasse kann nicht um andere Klassen erweitert werden.
Wenn eine Klasse die Klasse erweitert, die Sie als unveränderlich festlegen möchten, kann sich der Status der Klasse aufgrund von Vererbungsprinzipien ändern.
Klären Sie einfach "es kann sich ändern". Unterklassen können das Verhalten von Oberklassen überschreiben, z. B. das Überschreiben von Methoden (wie die Antwort templatetypedef / Ted Hop).
quelle
final
, kann ich nicht sehen, wie diese Antwort hilft.Wenn Sie es nicht endgültig machen, kann ich es erweitern und nicht veränderlich 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.
quelle
Zum Erstellen einer unveränderlichen Klasse ist es nicht zwingend erforderlich, die Klasse als endgültig zu markieren.
Lassen Sie mich eines dieser Beispiele aus den Java-Klassen selbst nehmen. Die "BigInteger" -Klasse ist unveränderlich, aber nicht endgültig.
Tatsächlich ist Unveränderlichkeit ein Konzept, nach dem das erstellte Objekt dann nicht geändert werden kann.
Nehmen wir an, aus JVM-Sicht müssen alle Threads aus JVM-Sicht dieselbe Kopie des Objekts gemeinsam nutzen und es ist vollständig erstellt, bevor ein Thread darauf zugreift und sich der Status des Objekts nach seiner Erstellung nicht ändert.
Unveränderlichkeit bedeutet, dass Sie den Status des Objekts nach seiner Erstellung nicht mehr ändern können. Dies wird durch drei Daumenregeln erreicht, mit denen der Compiler erkennt, dass die Klasse unveränderlich ist und wie folgt lautet: -
Weitere Informationen finden Sie unter der folgenden URL
http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html
quelle
Angenommen, Sie haben die folgende Klasse:
import java.util.ArrayList; import java.util.Date; import java.util.List; public class PaymentImmutable { private final Long id; private final List<String> details; private final Date paymentDate; private final String notes; public PaymentImmutable (Long id, List<String> details, Date paymentDate, String notes) { this.id = id; this.notes = notes; this.paymentDate = paymentDate == null ? null : new Date(paymentDate.getTime()); if (details != null) { this.details = new ArrayList<String>(); for(String d : details) { this.details.add(d); } } else { this.details = null; } } public Long getId() { return this.id; } public List<String> getDetails() { if(this.details != null) { List<String> detailsForOutside = new ArrayList<String>(); for(String d: this.details) { detailsForOutside.add(d); } return detailsForOutside; } else { return null; } } }
Dann verlängern Sie es und brechen seine Unveränderlichkeit.
public class PaymentChild extends PaymentImmutable { private List<String> temp; public PaymentChild(Long id, List<String> details, Date paymentDate, String notes) { super(id, details, paymentDate, notes); this.temp = details; } @Override public List<String> getDetails() { return temp; } }
Hier testen wir es:
public class Demo { public static void main(String[] args) { List<String> details = new ArrayList<>(); details.add("a"); details.add("b"); PaymentImmutable immutableParent = new PaymentImmutable(1L, details, new Date(), "notes"); PaymentImmutable notImmutableChild = new PaymentChild(1L, details, new Date(), "notes"); details.add("some value"); System.out.println(immutableParent.getDetails()); System.out.println(notImmutableChild.getDetails()); } }
Das Ausgabeergebnis ist:
Wie Sie sehen können, können untergeordnete Klassen veränderbar sein, während die ursprüngliche Klasse ihre Unveränderlichkeit beibehält. Folglich können Sie in Ihrem Entwurf nicht sicher sein, dass das von Ihnen verwendete Objekt unveränderlich ist, es sei denn, Sie machen Ihre Klasse endgültig.
quelle
Angenommen, die folgende Klasse wäre 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, weil selbst Unterklassen nicht geändert werden können
mThing
. Eine Unterklasse kann jedoch veränderlich 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; } }
Jetzt kann
Foo
nicht mehr garantiert werden, dass ein Objekt, das einer Variablen vom Typ zugewiesen werden kann, mmeable ist. Dies kann Probleme mit Dingen wie Hashing, Gleichheit, Parallelität usw. verursachen.quelle
Design an sich hat keinen Wert. Design wird immer verwendet, um ein Ziel zu erreichen. Was ist das Ziel hier? Wollen wir die Anzahl der Überraschungen im Code reduzieren? Wollen wir Fehler verhindern? Befolgen wir blind Regeln?
Auch Design ist immer mit Kosten verbunden. Jedes Design, das den Namen verdient, bedeutet, dass Sie einen Zielkonflikt haben .
In diesem Sinne müssen Sie Antworten auf diese Fragen finden:
Angenommen, Sie haben viele Nachwuchsentwickler in Ihrem Team. Sie werden verzweifelt jede dumme Sache versuchen, nur weil sie noch keine guten Lösungen für ihre Probleme kennen. Wenn die Klasse endgültig ist, können Fehler (gut) vermieden, aber auch "clevere" Lösungen gefunden werden, z. B. das Kopieren all dieser Klassen in veränderbare Klassen überall im Code.
Auf der anderen Seite wird es sehr schwierig sein, eine Klasse zu erstellen,
final
nachdem sie überall verwendet wird, aber es ist einfach, einefinal
Klasse nicht zu erstellen.final
später zu wenn Sie herausfinden, dass Sie sie erweitern müssen.Wenn Sie Schnittstellen ordnungsgemäß verwenden, können Sie das Problem "Ich muss dieses veränderbar machen" vermeiden, indem Sie immer die Schnittstelle verwenden und später bei Bedarf eine veränderbare Implementierung hinzufügen.
Fazit: Für diese Antwort gibt es keine "beste" Lösung. Es hängt davon ab, welchen Preis Sie bereit sind und welchen Sie zahlen müssen.
quelle
Die Standardbedeutung von equals () entspricht der referenziellen 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
quelle