Warum brauchen wir unveränderliche Klasse?


83

Ich kann nicht verstehen, in welchen Szenarien wir eine unveränderliche Klasse brauchen.
Haben Sie jemals eine solche Anforderung gestellt? oder können Sie uns bitte ein reales Beispiel geben, wo wir dieses Muster verwenden sollten.


13
Ich bin überrascht, dass niemand dieses Exemplar als Duplikat bezeichnet hat. Scheint, als ob ihr nur versucht, einfache Punkte zu sammeln ... :) (1) stackoverflow.com/questions/1284727/mutable-or-immutable-class (2) stackoverflow.com/questions/3162665/immutable-class ( 3) stackoverflow.com/questions/752280/…
Javid Jamae

Antworten:


82

Die anderen Antworten scheinen sich zu sehr darauf zu konzentrieren, zu erklären, warum Unveränderlichkeit gut ist. Es ist sehr gut und ich benutze es wann immer möglich. Dies ist jedoch nicht Ihre Frage . Ich werde Ihre Frage Punkt für Punkt beantworten, um sicherzustellen, dass Sie die Antworten und Beispiele erhalten, die Sie benötigen.

Ich kann nicht verstehen, in welchen Szenarien wir eine unveränderliche Klasse brauchen.

"Bedürfnis" ist hier ein relativer Begriff. Unveränderliche Klassen sind ein Entwurfsmuster, das wie jedes Paradigma / Muster / Werkzeug dazu dient, das Erstellen von Software zu vereinfachen. Ebenso wurde viel Code geschrieben, bevor das OO-Paradigma aufkam, aber zähle mich zu den Programmierern, die OO "brauchen" . Unveränderliche Klassen wie OO werden nicht unbedingt benötigt , aber ich werde so tun, als ob ich sie brauche.

Haben Sie jemals eine solche Anforderung gestellt?

Wenn Sie die Objekte in der Problemdomäne nicht mit der richtigen Perspektive betrachten, sehen Sie möglicherweise keine Anforderung für ein unveränderliches Objekt. Es ist leicht zu glauben, dass für eine Problemdomäne keine unveränderlichen Klassen erforderlich sind, wenn Sie nicht wissen, wann Sie sie vorteilhaft verwenden sollen.

Ich verwende oft unveränderliche Klassen, in denen ich ein bestimmtes Objekt in meiner Problemdomäne als Wert oder feste Instanz betrachte . Diese Vorstellung hängt manchmal von der Perspektive oder dem Standpunkt ab, aber im Idealfall ist es einfach, in die richtige Perspektive zu wechseln, um gute Kandidatenobjekte zu identifizieren.

Sie können sich ein besseres Bild davon machen, wo unveränderliche Objekte wirklich nützlich sind (wenn nicht unbedingt erforderlich), indem Sie verschiedene Bücher / Online-Artikel lesen, um ein gutes Gefühl dafür zu entwickeln, wie man über unveränderliche Klassen denkt. Ein guter Artikel für den Einstieg ist die Java-Theorie und -Praxis: Mutieren oder nicht mutieren?

Ich werde versuchen, im Folgenden einige Beispiele zu nennen, wie man Objekte in verschiedenen Perspektiven (veränderlich oder unveränderlich) sehen kann, um zu verdeutlichen, was ich unter Perspektive verstehe.

... können Sie uns bitte ein reales Beispiel geben, wo wir dieses Muster verwenden sollten.

Da Sie nach echten Beispielen gefragt haben, gebe ich Ihnen einige, aber beginnen wir zunächst mit einigen klassischen Beispielen.

Klassische Wertobjekte

Zeichenfolgen und Ganzzahlen werden oft als Werte betrachtet. Daher ist es nicht überraschend, dass die String-Klasse und die Integer-Wrapper-Klasse (sowie die anderen Wrapper-Klassen) in Java unveränderlich sind. Eine Farbe wird normalerweise als Wert betrachtet, daher die unveränderliche Farbklasse.

Gegenbeispiel

Im Gegensatz dazu wird ein Auto normalerweise nicht als Wertobjekt betrachtet. Das Modellieren eines Autos bedeutet normalerweise das Erstellen einer Klasse mit sich änderndem Zustand (Kilometerzähler, Geschwindigkeit, Kraftstoffstand usw.). Es gibt jedoch einige Domänen, in denen das Auto ein Wertobjekt sein kann. Beispielsweise kann ein Auto (oder speziell ein Automodell) als Wertobjekt in einer App betrachtet werden, um das richtige Motoröl für ein bestimmtes Fahrzeug nachzuschlagen.

Kartenspielen

Hast du jemals ein Spielkartenprogramm geschrieben? Ich tat. Ich hätte eine Spielkarte als veränderbares Objekt mit veränderlicher Farbe und Rang darstellen können. Eine Draw-Poker-Hand kann aus 5 festen Instanzen bestehen, bei denen das Ersetzen der 5. Karte in meiner Hand bedeuten würde, dass die 5. Spielkarteninstanz durch Ändern der Farbe und des Ranges der Ivars in eine neue Karte umgewandelt wird.

Ich neige jedoch dazu, eine Spielkarte als unveränderliches Objekt zu betrachten, das nach seiner Erstellung eine feste, unveränderliche Farbe und einen festen Rang hat. Meine Draw Poker Hand würde 5 Instanzen umfassen und das Ersetzen einer Karte in meiner Hand würde das Abwerfen einer dieser Instanzen und das Hinzufügen einer neuen zufälligen Instanz zu meiner Hand beinhalten.

Kartenprojektion

Ein letztes Beispiel ist, als ich an einem Kartencode gearbeitet habe, bei dem sich die Karte in verschiedenen Projektionen anzeigen könnte . Der ursprüngliche Code ließ die Karte eine feste, aber veränderbare Projektionsinstanz verwenden (wie die veränderbare Spielkarte oben). Das Ändern der Kartenprojektion bedeutete, die ivars der Kartenprojektionsinstanz (Projektionstyp, Mittelpunkt, Zoom usw.) zu mutieren.

Ich fand das Design jedoch einfacher, wenn ich mir eine Projektion als unveränderlichen Wert oder feste Instanz vorstellte. Das Ändern der Kartenprojektion bedeutete, dass die Kartenreferenz auf eine andere Projektionsinstanz verweist, anstatt die feste Projektionsinstanz der Karte zu mutieren. Dies machte es auch einfacher, benannte Projektionen wie z MERCATOR_WORLD_VIEW.


42

Unveränderliche Klassen sind im Allgemeinen viel einfacher zu entwerfen, zu implementieren und korrekt zu verwenden . Ein Beispiel ist String: Die Implementierung von java.lang.Stringist wesentlich einfacher als die von std::stringC ++, hauptsächlich aufgrund seiner Unveränderlichkeit.

Ein besonderer Bereich, in dem Unveränderlichkeit einen besonders großen Unterschied macht, ist die Parallelität: Unveränderliche Objekte können sicher von mehreren Threads gemeinsam genutzt werden , während veränderbare Objekte durch sorgfältiges Design und Implementierung threadsicher gemacht werden müssen - normalerweise ist dies keine triviale Aufgabe.

Update: Effective Java 2nd Edition behebt dieses Problem ausführlich - siehe Punkt 15: Minimierung der Veränderlichkeit .

Siehe auch diese verwandten Beiträge:


39

Effektives Java von Joshua Bloch beschreibt mehrere Gründe, unveränderliche Klassen zu schreiben:

  • Einfachheit - jede Klasse befindet sich nur in einem Zustand
  • Thread-sicher - Da der Status nicht geändert werden kann, ist keine Synchronisierung erforderlich
  • Das Schreiben in einem unveränderlichen Stil kann zu robusterem Code führen. Stellen Sie sich vor, Strings wären nicht unveränderlich. Bei allen Getter-Methoden, die einen String zurückgeben, muss die Implementierung eine defensive Kopie erstellen, bevor der String zurückgegeben wird. Andernfalls kann ein Client diesen Status des Objekts versehentlich oder böswillig beschädigen.

Im Allgemeinen empfiehlt es sich, ein Objekt unveränderlich zu machen, es sei denn, dies führt zu schwerwiegenden Leistungsproblemen. Unter solchen Umständen können veränderbare Builder-Objekte verwendet werden, um unveränderliche Objekte zu erstellen, z. B. StringBuilder


1
Jede Klasse befindet sich in einem Zustand oder in jedem Objekt?
Tushar Thakur

@TusharThakur, jedes Objekt.
Joker

17

Hashmaps sind ein klassisches Beispiel. Der Schlüssel zu einer Karte muss unbedingt unveränderlich sein. Wenn der Schlüssel nicht unveränderlich ist und Sie einen Wert für den Schlüssel so ändern, dass hashCode () zu einem neuen Wert führt, ist die Zuordnung jetzt fehlerhaft (ein Schlüssel befindet sich jetzt an der falschen Stelle in der Hash-Tabelle).


3
Ich würde eher sagen, dass der Schlüssel nicht geändert werden muss. Es gibt jedoch keine offizielle Anforderung, dass es unveränderlich sein muss.
Péter Török

Ich bin mir nicht sicher, was du mit "offiziell" meinst.
Kirk Woll

1
Ich denke, er meint, die einzige wirkliche Voraussetzung ist, dass die Schlüssel NICHT geändert werden SOLLTEN ... nicht, dass sie überhaupt nicht geändert werden können (über Unveränderlichkeit). Eine einfache Möglichkeit, das Ändern von Schlüsseln zu verhindern, besteht natürlich darin, sie zunächst unveränderlich zu machen! :-)
Platinum Azure

2
Beispiel : download.oracle.com/javase/6/docs/api/java/util/Map.html : "Hinweis: Wenn veränderbare Objekte als Kartenschlüssel verwendet werden, ist besondere Vorsicht geboten." Das heißt, veränderbare Objekte können als Schlüssel verwendet werden.
Péter Török

8

Java ist praktisch eine Referenz. Manchmal wird eine Instanz mehrmals referenziert. Wenn Sie eine solche Instanz ändern, wird sie in allen Referenzen wiedergegeben. Manchmal möchten Sie dies einfach nicht, um die Robustheit und Thread-Sicherheit zu verbessern. Dann ist eine unveränderliche Klasse nützlich, so dass man gezwungen ist, eine neue Instanz zu erstellen und sie der aktuellen Referenz neu zuzuweisen. Auf diese Weise bleibt die ursprüngliche Instanz der anderen Referenzen unberührt.

Stellen Sie sich vor, wie Java aussehen würde, wenn Stringes veränderlich wäre.


12
Oder ob Dateund Calendarveränderlich waren. Oh, warte, sie sind, OH SH
gustafc

1
@gustafc: Der veränderbare Kalender ist in Anbetracht seiner Aufgabe, Datumsberechnungen durchzuführen, in Ordnung (könnte durch Zurücksenden von Kopien erfolgen, aber wenn man bedenkt, wie schwer ein Kalender ist, ist dies besser). aber Date - ja, das ist böse.
Michael Borgwardt

Bei einigen JRE-Implementierungen Stringist veränderlich! (Hinweis: einige ältere JRockit-Versionen). Das Aufrufen von string.trim () führte dazu, dass die ursprüngliche Zeichenfolge
gekürzt wurde

6
@ Salandur: Dann ist es keine "JRE-Implementierung". Es ist eine Implementierung von etwas, das einer JRE ähnelt, es aber nicht ist.
Mark Peters

@ Mark Peters: eine sehr wahre Aussage
Salandur

6

Wir brauchen per se keine unveränderlichen Klassen, aber sie können sicherlich einige Programmieraufgaben erleichtern, insbesondere wenn mehrere Threads beteiligt sind. Sie müssen keine Sperre durchführen, um auf ein unveränderliches Objekt zuzugreifen, und alle Fakten, die Sie bereits zu einem solchen Objekt ermittelt haben, werden auch in Zukunft zutreffen.


6

Nehmen wir einen Extremfall: ganzzahlige Konstanten. Wenn ich eine Aussage wie "x = x + 1" schreibe, möchte ich 100% sicher sein, dass die Zahl "1" nicht irgendwie zu 2 wird, egal was irgendwo anders im Programm passiert.

Okay, Integer-Konstanten sind keine Klasse, aber das Konzept ist dasselbe. Angenommen, ich schreibe:

String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);

Sieht einfach aus. Aber wenn Strings nicht unveränderlich wären, müsste ich die Möglichkeit in Betracht ziehen, dass getCustomerName die customerId ändern könnte, sodass ich beim Aufruf von getCustomerBalance den Kontostand für einen anderen Kunden erhalte. Jetzt könnten Sie sagen: "Warum in aller Welt sollte jemand, der eine getCustomerName-Funktion schreibt, die ID ändern? Das würde keinen Sinn ergeben." Aber genau hier könnten Sie in Schwierigkeiten geraten. Die Person, die den obigen Code schreibt, könnte es als offensichtlich ansehen, dass die Funktionen den Parameter nicht ändern würden. Dann kommt jemand, der eine andere Verwendung dieser Funktion ändern muss, um den Fall zu behandeln, in dem ein Kunde mehrere Konten unter demselben Namen hat. Und er sagt: "Oh, hier ist diese praktische Funktion getCustomer name, die den Namen bereits nachschlägt. I '

Unveränderlichkeit bedeutet einfach, dass eine bestimmte Klasse von Objekten Konstanten sind, und wir können sie als Konstanten behandeln.

(Natürlich könnte der Benutzer einer Variablen ein anderes "konstantes Objekt" zuweisen. Jemand kann String s = "Hallo" schreiben und später s = "Auf Wiedersehen" schreiben. Wenn ich die Variable nicht endgültig mache, kann ich nicht sicher sein dass es nicht in meinem eigenen Codeblock geändert wird. Genau wie Ganzzahlkonstanten versichern Sie mir, dass "1" immer die gleiche Zahl ist, aber nicht, dass "x = 1" niemals durch Schreiben von "x = 2" geändert wird. Aber ich kann sicher sein, dass, wenn ich ein Handle für ein unveränderliches Objekt habe, keine Funktion, an die ich es übergebe, es an mir ändern kann, oder dass, wenn ich zwei Kopien davon mache, eine Änderung an der Variablen, die eine Kopie enthält, das nicht ändert andere. Etc.


5

Es gibt verschiedene Gründe für die Unveränderlichkeit:

  • Thread-Sicherheit: Unveränderliche Objekte können weder geändert noch ihr interner Status geändert werden, sodass keine Synchronisierung erforderlich ist.
  • Es garantiert auch, dass alles, was ich (über ein Netzwerk) sende, in dem Zustand sein muss, in dem es zuvor gesendet wurde. Es bedeutet, dass niemand (Lauscher) kommen und zufällige Daten zu meinem unveränderlichen Satz hinzufügen kann.
  • Es ist auch einfacher zu entwickeln. Sie garantieren, dass keine Unterklassen existieren, wenn ein Objekt unveränderlich ist. ZB eine StringKlasse.

Wenn Sie also Daten über einen Netzwerkdienst senden möchten und die Garantie haben möchten , dass Ihr Ergebnis genau dem entspricht, das Sie gesendet haben, setzen Sie es als unveränderlich.


Ich verstehe weder den Teil über das Senden über das Netzwerk noch den Teil, in dem Sie sagen, dass eine unveränderliche Klasse nicht erweitert werden kann.
Aioobe

@aioobe, Senden von Daten über das Netzwerk, z. B. Webdienste, RMI usw. Und für erweiterte können Sie eine unveränderliche String-Klasse oder ein ImmutableSet, ImmutableList usw. nicht erweitern.
Buhake Sindi

Die Möglichkeit, String, ImmutableSet, ImmutableList usw. nicht zu erweitern, ist ein völlig orthogonales Problem für die Unveränderlichkeit. Nicht alle finalin Java gekennzeichneten Klassen sind unveränderlich, und nicht alle unveränderlichen Klassen sind unveränderlich final.
NUR MEINE RICHTIGE MEINUNG

5

Ich werde dies aus einer anderen Perspektive angreifen. Ich finde, unveränderliche Objekte erleichtern mir das Leben beim Lesen von Code.

Wenn ich ein veränderliches Objekt habe, bin ich mir nie sicher, welchen Wert es hat, wenn es jemals außerhalb meines unmittelbaren Bereichs verwendet wird. Angenommen, ich erstelle MyMutableObjectdie lokalen Variablen einer Methode, fülle sie mit Werten aus und übergebe sie dann an fünf andere Methoden. JEDE dieser Methoden kann den Status meines Objekts ändern, daher muss eines von zwei Dingen auftreten:

  1. Ich muss die Körper von fünf zusätzlichen Methoden im Auge behalten, während ich über die Logik meines Codes nachdenke.
  2. Ich muss fünf verschwenderische defensive Kopien meines Objekts erstellen, um sicherzustellen, dass die richtigen Werte an jede Methode übergeben werden.

Das erste macht es schwierig, über meinen Code nachzudenken. Die zweite führt dazu, dass mein Code an Leistung verliert - ich ahme im Grunde genommen ein unveränderliches Objekt mit Copy-on-Write-Semantik nach, mache es aber immer wieder, unabhängig davon, ob die aufgerufenen Methoden den Status meines Objekts tatsächlich ändern oder nicht.

Wenn ich stattdessen verwende MyImmutableObject, kann ich sicher sein, dass ich die Werte für die Lebensdauer meiner Methode festlege. Es gibt keine "gruselige Aktion in der Ferne", die es unter mir ändern könnte, und ich muss keine defensiven Kopien meines Objekts erstellen, bevor ich die fünf anderen Methoden aufrufe. Wenn die anderen Methoden für ihre Zwecke zu ändern Dinge wollen sie haben , um die Kopie zu machen - aber sie dies nur tun , wenn sie wirklich eine Kopie machen müssen (im Gegensatz zu meinen es vor jedem einzelnen externen Methodenaufruf tun). Ich erspare mir die mentalen Ressourcen, um Methoden zu verfolgen, die möglicherweise nicht einmal in meiner aktuellen Quelldatei enthalten sind, und ich erspare dem System den Aufwand, für alle Fälle unnötige defensive Kopien zu erstellen.

(Wenn ich mich außerhalb der Java-Welt und beispielsweise in der C ++ - Welt befinde, kann ich noch kniffliger werden. Ich kann die Objekte so aussehen lassen, als wären sie veränderlich, aber hinter den Kulissen lassen sie sie transparent auf jede klonen Art von Zustandsänderung - das ist Copy-on-Write - mit niemandem, der klüger ist.)


Ein Vorteil von veränderlichen Werttypen in Sprachen, die sie unterstützen und keine dauerhaften Verweise auf sie zulassen, besteht darin, dass Sie die von Ihnen beschriebenen Vorteile erhalten (wenn ein veränderlicher Referenztyp als Verweis auf eine Routine übergeben wird, kann diese Routine ihn während der Ausführung ändern , kann aber keine Änderungen nach der Rückkehr verursachen), ohne die Unbeholfenheit, die unveränderliche Objekte häufig haben.
Supercat

5

Meine 2 Cent für zukünftige Besucher:


2 Szenarien, in denen unveränderliche Objekte eine gute Wahl sind:

Im Multithreading

Parallelitätsprobleme in einer Multithread-Umgebung können sehr gut durch Synchronisierung gelöst werden, aber die Synchronisierung ist eine kostspielige Angelegenheit (würde hier nicht auf das "Warum" eingehen). Wenn Sie also unveränderliche Objekte verwenden, gibt es keine Synchronisierung, um das Parallelitätsproblem aufgrund des Status von zu lösen Unveränderliche Objekte können nicht geändert werden. Wenn der Status nicht geändert werden kann, können alle Threads nahtlos auf das Objekt zugreifen. Unveränderliche Objekte sind daher eine gute Wahl für gemeinsam genutzte Objekte in einer Umgebung mit mehreren Threads.


Als Schlüssel für Hash-basierte Sammlungen

Eines der wichtigsten Dinge, die bei der Arbeit mit Hash-basierten Auflistungen zu beachten sind, ist, dass der Schlüssel so sein sollte, dass er hashCode()für die Lebensdauer des Objekts immer den gleichen Wert zurückgibt. Wenn dieser Wert geändert wird, wird ein alter Eintrag in die Hash-basierte Auflistung vorgenommen Die Verwendung dieses Objekts kann nicht abgerufen werden, daher würde dies zu einem Speicherverlust führen. Da der Status unveränderlicher Objekte nicht geändert werden kann, treffen sie eine gute Wahl als Schlüssel für die Hash-basierte Sammlung. Wenn Sie also ein unveränderliches Objekt als Schlüssel für die Hash-basierte Erfassung verwenden, können Sie sicher sein, dass dadurch kein Speicherverlust auftritt (natürlich kann es immer noch zu einem Speicherverlust kommen, wenn das als Schlüssel verwendete Objekt nicht von irgendwoher referenziert wird sonst, aber darum geht es hier nicht).


2

Unveränderliche Objekte sind Instanzen, deren Status sich nach dem Start nicht ändern. Die Verwendung solcher Objekte ist anforderungsspezifisch.

Die unveränderliche Klasse eignet sich für Caching-Zwecke und ist threadsicher.


2

Durch die Unveränderlichkeit können Sie sicher sein, dass sich das Verhalten / der Zustand des zugrunde liegenden unveränderlichen Objekts nicht ändert, wodurch Sie den zusätzlichen Vorteil erhalten, zusätzliche Operationen auszuführen:

  • Sie können problemlos mehrere Kerne / Verarbeitungen ( gleichzeitige / parallele Verarbeitung ) verwenden (da die Reihenfolge der Vorgänge keine Rolle mehr spielt).

  • Kann Caching für teure Vorgänge durchführen (da Sie sich des gleichen
    Ergebnisses sicher sind ).

  • Kann problemlos debuggen (da die Laufhistorie kein Problem
    mehr darstellt)


1

Die Verwendung des letzten Schlüsselworts macht nicht unbedingt etwas unveränderlich:

public class Scratchpad {
    public static void main(String[] args) throws Exception {
        SomeData sd = new SomeData("foo");
        System.out.println(sd.data); //prints "foo"
        voodoo(sd, "data", "bar");
        System.out.println(sd.data); //prints "bar"
    }

    private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
        Field f = SomeData.class.getDeclaredField("data");
        f.setAccessible(true);
        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
        f.set(obj, "bar");
    }
}

class SomeData {
    final String data;
    SomeData(String data) {
        this.data = data;
    }
}

Nur ein Beispiel, um zu demonstrieren, dass das Schlüsselwort "final" dazu dient, Programmiererfehler zu vermeiden, und nicht viel mehr. Während die Neuzuweisung eines Werts ohne endgültiges Schlüsselwort leicht versehentlich erfolgen kann, müsste das Abrufen dieser Länge zum Ändern eines Werts absichtlich erfolgen. Es dient zur Dokumentation und zur Vermeidung von Programmierfehlern.


Beachten Sie, dass dies die Frage nicht direkt beantwortet, da es nicht um endgültig ging. es ging um unveränderliche Klassen. Sie können eine unveränderliche Klasse ohne das endgültige Schlüsselwort verwenden, indem Sie die entsprechenden Zugriffsbeschränkungen verwenden.
Mark Peters

1

Unveränderliche Datenstrukturen können auch beim Codieren rekursiver Algorithmen hilfreich sein. Angenommen, Sie versuchen, ein 3SAT- Problem zu lösen . Eine Möglichkeit besteht darin, Folgendes zu tun:

  • Wählen Sie eine nicht zugewiesene Variable.
  • Geben Sie den Wert TRUE an. Vereinfachen Sie die Instanz, indem Sie jetzt erfüllte Klauseln entfernen und die einfachere Instanz erneut lösen.
  • Wenn die Rekursion im Fall TRUE fehlgeschlagen ist, weisen Sie stattdessen diese Variable FALSE zu. Vereinfachen Sie diese neue Instanz und wiederholen Sie sie, um sie zu lösen.

Wenn Sie eine veränderbare Struktur haben, um das Problem darzustellen, müssen Sie beim Vereinfachen der Instanz im TRUE-Zweig entweder:

  • Behalten Sie alle Änderungen im Auge, die Sie vornehmen, und machen Sie sie rückgängig, sobald Sie feststellen, dass das Problem nicht gelöst werden kann. Dies hat einen hohen Overhead, da Ihre Rekursion ziemlich tief gehen kann und das Codieren schwierig ist.
  • Erstellen Sie eine Kopie der Instanz und ändern Sie die Kopie. Dies ist langsam, denn wenn Ihre Rekursion einige Dutzend Ebenen tief ist, müssen Sie viele, viele Kopien der Instanz erstellen.

Wenn Sie es jedoch auf clevere Weise codieren, können Sie eine unveränderliche Struktur haben, in der jede Operation eine aktualisierte (aber immer noch unveränderliche) Version des Problems zurückgibt (ähnlich wie String.replace- sie ersetzt nicht die Zeichenfolge, sondern gibt Ihnen nur eine neue ). Der naive Weg, dies zu implementieren, besteht darin, die "unveränderliche" Struktur einfach zu kopieren und bei jeder Änderung eine neue zu erstellen, um sie auf die zweite Lösung zu reduzieren, wenn Sie eine veränderbare haben, mit all dem Aufwand, aber Sie können es in mehr tun effizienter Weg.


1

Einer der Gründe für die "Notwendigkeit" unveränderlicher Klassen ist die Kombination, alles als Referenz zu übergeben und schreibgeschützte Ansichten eines Objekts (dh C ++ const) nicht zu unterstützen.

Betrachten Sie den einfachen Fall einer Klasse, die das Beobachtermuster unterstützt:

class Person {
    public string getName() { ... }
    public void registerForNameChange(NameChangedObserver o) { ... }
}

Wenn dies stringnicht unveränderlich wäre, könnte die PersonKlasse nicht registerForNameChange()korrekt implementiert werden, da jemand Folgendes schreiben und den Namen der Person effektiv ändern könnte, ohne eine Benachrichtigung auszulösen.

void foo(Person p) {
    p.getName().prepend("Mr. ");
}

In C ++ hat die Rückgabe von getName()a const std::string&den Effekt, dass die Referenz zurückgegeben wird und der Zugriff auf Mutatoren verhindert wird. Dies bedeutet, dass unveränderliche Klassen in diesem Kontext nicht erforderlich sind.



1

Ein Merkmal unveränderlicher Klassen, das noch nicht aufgerufen wurde: Das Speichern eines Verweises auf ein tief unveränderliches Klassenobjekt ist ein effizientes Mittel zum Speichern des gesamten darin enthaltenen Zustands. Angenommen, ich habe ein veränderliches Objekt, das ein tief unveränderliches Objekt verwendet, um Statusinformationen im Wert von 50.000 zu speichern. Angenommen, ich möchte 25 Mal eine "Kopie" meines ursprünglichen (veränderlichen) Objekts erstellen (z. B. für einen "Rückgängig" -Puffer). Der Status kann sich zwischen den Kopiervorgängen ändern, ist dies jedoch normalerweise nicht der Fall. Das Erstellen einer "Kopie" des veränderlichen Objekts würde lediglich das Kopieren eines Verweises auf seinen unveränderlichen Zustand erfordern, sodass 20 Kopien einfach 20 Verweise ergeben würden. Wenn der Status dagegen in veränderlichen Objekten im Wert von 50.000 gehalten würde, müsste jede der 25 Kopiervorgänge eine eigene Kopie von Daten im Wert von 50.000 erstellen. Das Halten aller 25 Kopien würde das Halten eines Megas im Wert von größtenteils duplizierten Daten erfordern. Obwohl die erste Kopieroperation eine Kopie der Daten erzeugen würde, die sich niemals ändern wird, und die anderen 24 Operationen theoretisch einfach darauf zurückgreifen könnten, gäbe es in den meisten Implementierungen keine Möglichkeit für das zweite Objekt, eine Kopie der Daten anzufordern Informationen, um zu wissen, dass bereits eine unveränderliche Kopie vorhanden ist (*).

(*) Ein Muster, das manchmal nützlich sein kann, besteht darin, dass veränderbare Objekte zwei Felder haben, um ihren Status beizubehalten - eines in veränderlicher Form und eines in unveränderlicher Form. Objekte können als veränderlich oder unveränderlich kopiert werden und würden mit dem einen oder anderen Referenzsatz beginnen. Sobald das Objekt seinen Status ändern möchte, kopiert es den unveränderlichen Verweis auf den veränderlichen (falls dies noch nicht geschehen ist) und macht den unveränderlichen ungültig. Wenn das Objekt als unveränderlich kopiert wird und seine unveränderliche Referenz nicht festgelegt ist, wird eine unveränderliche Kopie erstellt und die unveränderliche Referenz darauf hingewiesen. Dieser Ansatz erfordert ein paar mehr Kopiervorgänge als eine "vollwertige Kopie beim Schreiben" (z. B. die Aufforderung, ein Objekt zu kopieren, das seit der letzten Kopie mutiert wurde, würde einen Kopiervorgang erfordern).


1

Warum unveränderliche Klasse?

Sobald ein Objekt instanziiert wurde, kann sein Status während der Lebensdauer nicht mehr geändert werden. Das macht es auch threadsicher.

Beispiele:

Offensichtlich String, Integer und BigDecimal usw. Sobald diese Werte erstellt wurden, können sie nicht mehr auf Lebenszeit geändert werden.

Anwendungsfall: Sobald das Datenbankverbindungsobjekt mit seinen Konfigurationswerten erstellt wurde, müssen Sie möglicherweise seinen Status nicht mehr ändern, in dem Sie eine unveränderliche Klasse verwenden können


0

von Effective Java; Eine unveränderliche Klasse ist einfach eine Klasse, deren Instanzen nicht geändert werden können. Alle in jeder Instanz enthaltenen Informationen werden beim Erstellen bereitgestellt und sind für die Lebensdauer des Objekts festgelegt. Die Java-Plattformbibliotheken enthalten viele unveränderliche Klassen, einschließlich String, die primitiven Klassen in Boxen sowie BigInteger und BigDecimal. Dafür gibt es viele gute Gründe: Unveränderliche Klassen sind einfacher zu entwerfen, zu implementieren und zu verwenden als veränderbare Klassen. Sie sind weniger fehleranfällig und sicherer.

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.