[JAVA] Was ist ein unveränderliches Objekt? [Erklärung, wie man macht]

Ich habe gehört, dass unveränderliche Objekte sicher sind oder versuchen, unveränderliche Objekte so weit wie möglich zu erstellen, aber was ist das? Ich denke, es gibt viele Leute

Hier werde ich erklären, was ein unveränderliches Objekt ist und wie man ein unveränderliches Objekt herstellt. Unveränderliche Objekte können unabhängig von der Sprache erstellt werden, aber ich denke, ich werde es in Java erklären.

Die Erklärung ist für Anfänger hier, aber wenn Sie eine ausführlichere Erklärung lesen möchten, kaufen Sie bitte die folgenden Bücher. Effektive Java 3rd Edition

Was ist ein unveränderliches Objekt?

Einfach ausgedrückt handelt es sich um ein Objekt mit denselben Daten.

Ich weiß nicht, was es bedeutet, deshalb erkläre ich es mit Java String und StringBuilder. Schauen Sie sich zuerst den folgenden Code an


String str = "A";

for(int i = 0; i < 2 ; i++){
  str += "A";
}

System.out.println(str);

Das Ausgabeergebnis ist wie folgt


AAA

Dieser Code fügt einfach zweimal A zu str hinzu Im obigen Code wird str als String-Typ deklariert, aber die Deklaration von str als StringBuilder-Typ ändert das Ausgabeergebnis nicht.

Die Instanziierungsmethode unterscheidet sich jedoch zwischen String und StringBuilder. Mal sehen, welche Art von Instanz am Ende bleiben wird

** Für StringBuilder ** StringBuilder.png Da nur dieselbe Instanz wie das Ausgabeergebnis generiert wird, ist die letzte verbleibende Instanz nur eine AAA.

** Für String ** String.png Im Fall von String wird jedes Mal, wenn A hinzugefügt wird, eine weitere Instanz erstellt. Daher bleiben A-, AA- und AAA-Instanzen erhalten A- und AA-Instanzen sind nicht referenzierbar und unterliegen der GC

Unterschied zwischen String und StringBuilder

Wie Sie dem obigen Beispiel entnehmen können, hat StringBuilder den Wert einer neu geschriebenen Instanz, während String eine andere Instanz hat, ohne den Wert neu zu schreiben. Generieren der Farbe </ font> </ font>

StringBuilder erstellt nur eine Instanz und ist daher kostengünstiger Da String viele Male Instanzen erstellt hat, ist die Last entsprechend höher.

Wenn man das betrachtet, macht es keinen Sinn, String zu verwenden! Sie mögen fühlen, aber ** es ist ein Fehler **

Unveränderliche und variable Objekte

StringBuilder wird als veränderbares Objekt bezeichnet, da der Wert neu geschrieben wird String wird als unveränderliches Objekt bezeichnet, da es seinen Wert nicht neu geschrieben hat

Wie oben erwähnt, führen die Vorteile von StringBuilder zu einer verbesserten Leistung. In diesem Beispiel wird A nur zweimal kombiniert. Wenn dies jedoch das 10.000-fache ist, können die Kosten für die Instanzerstellung nicht zum Narren gehalten werden.

Obwohl es sich um dieselbe Instanz handelt, wird der Wert abhängig vom Timing neu geschrieben, z. B. A, AA, AAAAAAA. Dies ist ein großes Problem, und sobald Sie eine Änderung vornehmen, wirkt sich dies auch auf die Objekte aus, die darauf verweisen. 可変オブジェクト.png

Dies bedeutet, dass ** variable Objekte bei jeder Änderung ** </ font color> einige Nebenwirkungen haben

Was ist mit String? Das Erstellen mehrerer Instanzen kostet Geld, aber ein Objekt hat immer den gleichen Wert. Wenn Sie eine Instanz mit einem anderen Wert möchten, erstellen Sie eine andere Instanz Daher sind andere Objekte nicht betroffen. 不変オブジェクト.png Das heißt, ** Unveränderliche Objekte können verwendet werden, um sicherzustellen, dass sich ihre Werte nicht ändern ** </ font color>

Unveränderliche Objekte sind auch für die Multithread-Programmierung nützlich Unveränderliche Objekte ändern ihren Wert nicht, sodass Sie nicht mit synchronisierten ... Es ist ein variables Objekt, das in einer Multithread-Umgebung wichtig ist.

** Unveränderliche Objekte sind im Wesentlichen threadsicher ** </ font color>

Selbst wenn Sie ein variables Objekt erstellen, können Sie Unfälle vermeiden, indem Sie die variablen Teile so weit wie möglich reduzieren.

Wie man ein unveränderliches Objekt macht

Ich hoffe, Sie können anhand der bisherigen Erklärung sehen, wie nützlich unveränderliche Objekte sind. Von hier aus werde ich erklären, wie man ein unveränderliches Objekt tatsächlich erstellt.

Vererbung von Klassen verhindern

Wenn Sie zulassen, dass die Klasse vererbt wird, wird die Kapselung unterbrochen, z. B. vererbt und es werden seltsame Methoden hinzugefügt. Deklarieren Sie die Klasse so, dass sie nicht vererbt werden kann, da wir keine Probleme wie unveränderliche Objekte oder variable Objekte verursachen möchten, je nachdem, wie sie erstellt werden, obwohl sie denselben Typ haben.


final class TestClass{}

Wenn es sich um eine Klasse eines gekapselten Moduls handelt, ist es von außen nicht sichtbar, sodass es praktisch unmöglich ist, zu erben, ohne final hinzuzufügen. Wenn Sie jedoch nicht beabsichtigen, es zu erben, fügen Sie final hinzu, um zu erben Es ist üblich zu verbieten (das gleiche gilt für variable Objekte)

Machen Sie alle Felder konstant

Unveränderliche Objekte möchten nicht, dass ihre Werte geändert werden. Machen Sie sie daher zu allen Konstanten Stellen Sie außerdem die Sichtbarkeit so ein, dass die Variablen nicht direkt referenziert werden.


private final int num;
private final StringBuilder str;

Es ist kein Problem, wenn Sie unabhängig vom Basisdatentyp und Referenzdatentyp auf dieselbe Weise deklarieren. Im Fall des Referenzdatentyps ist es notwendig, die Behandlung ein wenig zu ändern, dies wird jedoch später beschrieben.

Mach keinen Mutator

Ein Mutator ist eine Operation, die Werte wie Setter neu schreibt. Natürlich benötigen Sie dazu keine Methode, da Sie den Wert nicht neu schreiben möchten

Achten Sie auf Getter

Schauen Sie sich zuerst den folgenden Code an


public int getNum(){
  return num;
}

public StringBuilder getStr(){
  return str;
}

Der Rückgabewert der Methode getNum ist der Basisdatentyp, und der Rückgabewert der Methode getStr ist der Referenzdatentyp. Für den Basisdatentyp wird eine Kopie des Werts von num zurückgegeben, sodass kein Problem vorliegt. Beim Referenzdatentyp tritt jedoch ein Problem auf, da die Referenz unverändert zurückgegeben wird.

Da die Referenz der Instanz auf der Seite abgerufen werden kann, die die getStr-Methode aufgerufen hat, kann der Wert des Inhalts der str-Instanz neu geschrieben werden.

Um dies zu verhindern, habe ich versucht, den vorherigen Code zu verbessern


public String getStr(){
  String result = str.toString;
  return result;
}

Die StringBuilder-Klasse hatte eine Methode namens toString, die ein variables Objekt in ein unveränderliches Objekt umwandelt. Deshalb habe ich versucht, es zu verwenden. Dieser Typkonvertierungsteil ist jedoch in der Beschreibung "Achten Sie auf Getter" nicht sehr wichtig. Wichtig ist, dass Werte kopiert und zurückgegeben werden </ font color>

Erstellen Sie eine weitere Kopierinstanz und geben Sie einen Verweis auf die Kopierinstanz zurück Auf diese Weise kann der Aufrufer der getStr-Methode die Kopierinstanz auf beliebige Weise manipulieren, ohne jedoch die ursprüngliche Instanz zu beeinträchtigen.

Diese Technik wird als ** defensive Kopie ** bezeichnet Durch die Verwendung einer defensiven Kopie kann selbst eine Klasse, die variable Objekte wie StringBuilder verarbeitet, die Klasse zu einem unveränderlichen Objekt machen.

Es gibt einige Einschränkungen bei der Verwendung von Defensivkopien ① Die Leistung kann sich verschlechtern Im Fall eines Arrays, einer Liste usw. kann die Leistung beeinträchtigt werden, da der gesamte Inhalt kopiert werden muss. Aber denken Sie daran, dass unveränderliche Objekte den Preis wert sind. ② Verwenden Sie nicht die Klonmethode Die Java-Klonmethode weist einige Probleme auf. Verwenden Sie sie daher nicht, wenn Sie eine defensive Kopie erstellen

Beispielcode

Ich werde den Beispielcode des bisher erläuterten unveränderlichen Objekts einfügen (Der Konstruktorteil wurde hinzugefügt)


final class TestClass{

  private final int num;
  private final StringBuilder str;

  public TestClass(int num,StringBuilder str){
    this.num = num;
    //Machen Sie hier auch eine defensive Kopie
    this.str = new StringBuilder(str);
  }

  public int getNum(){
    return num;
  }

  public String getStr(){
    String result = str.toString;
    return result;
  }

}
  • Hinzugefügt am 1. Juli 2020 (Danke an @ saka1029 für den Hinweis) Sie müssen auch eine defensive Kopie erstellen, wenn Sie ein veränderbares Objekt als Argument im Konstruktor erhalten Wenn Sie dies nicht tun, haben der Aufrufer und die Testklasse dieselbe Referenz und Sie können den Wert neu schreiben. ** Verwenden Sie eine defensive Kopie, wenn Sie variable Objekte empfangen und übergeben ** </ font color>

Bonus

Im obigen Beispielcode ist der Konstruktor öffentlich, aber Effective Java empfiehlt eine statische Factory. Dies gilt auch für die aktuelle API-Entwicklung Lassen Sie uns den Beispielcode früher umschreiben


final class TestClass{

  private final int num;
  private final StringBuilder str;

  //Verbot der Verwendung von Konstruktoren von außen
  //Der folgende Code kann auf der Anruferseite nicht verwendet werden
  // TestClass tc = new TestClass(10,"AAA");
  private TestClass(int num,StringBuilder str){
    this.num = num;
    this.str = new StringBuilder(str);
  }


  //static factory
  public static final TestClass newInstance(int num,StringBuilder str){
    return new TestClass(num,str);
  }

  public int getNum(){
    return num;
  }

  public String getStr(){
    String result = str.toString;
    return result;
  }

}

Der aufrufende Code sieht so aus


TestClass tc = TestClass.newInstance(10,"AAA");

Im Fall des obigen Codes handelt es sich nicht um einen Singleton, da bei jedem Aufruf der newInstance-Methode, bei der es sich um eine statische Factory handelt, eine neue Instanz vom Typ TestClass erstellt wird. Wenn Sie jedoch kein Singleton sein möchten, können Sie diese Technik verwenden

Wenn Sie es zu einem Singleton machen möchten, verwenden Sie nicht new in der statischen Factory-Methode, sondern erstellen Sie einfach eine Instanz im Voraus und kehren Sie zurück

Mit dem Aufkommen von DI-Containern kann die Wahrscheinlichkeit verringert werden, dass eine statische Fabrik direkt aufgerufen wird, aber diese Technik ist immer noch aktiv.

Zusammenfassung

Ich stellte vor, was ein unveränderliches Objekt ist und wie man es herstellt.

Es ist keine Übertreibung zu sagen, dass es kein System gibt, das in keinem System unveränderliche Objekte benötigt. Denken Sie also daran und verwenden Sie es.

Recommended Posts