Unveränderliche Java-Objekte

Objekte, deren Zustand nach ihrer Erstellung nicht geändert werden kann, werden als unveränderliche Objekte bezeichnet. Es gilt als wirksame Strategie, Objekte unveränderlich zu machen, wenn sie in Multithread-Anwendungen verwendet werden sollen. Die in Java integrierten Strings und Integers sind alle unveränderliche Objekte und daher muss es eine solide Begründung dafür geben. Versuchen wir in diesem Artikel, dies zu untersuchen.

Fragen:

  • Warum sollten wir ein Objekt erstellen, dessen Zustand nicht geändert werden kann?
  • Wann und wie werden unveränderliche Objekte verwendet?
  • Wie mache ich ein Java-Objekt unveränderlich?
  • Der Hauptzweck der Unveränderlichkeit von Objekten besteht in der Thread-Sicherheit und Parallelität. Indem wir ein Objekt unveränderlich machen, können wir garantieren, dass sich der Zustand unseres Objekts gegenüber seinem ursprünglichen/invarianten Zustand nicht ändert. Dadurch können mehrere Threads unveränderliche Threads gemeinsam nutzen, ohne befürchten zu müssen, dass ein Thread seinen ursprünglichen Zustand ändert.

    Es ist wichtig zu verstehen, dass immutable in Java keine Klasse/Objekt/Schlüsselwort ist und daher unsere Objekte nicht unveränderlich sind, es sei denn, wir erzwingen dies mit einigen Techniken. Daher liegt es in der Verantwortung der Entwickler, bei Bedarf Unveränderlichkeit in Objekten durchzusetzen. Beginnen wir mit einem veränderlichen Objekt und sehen wir uns an, wie man ein Objekt unveränderlich macht.

    import java.util.Date;
    class EmployeeMut {
       private String name;
       private Date salaryDate;
       private Double salary;
        public EmployeeMut(String name, Date salaryDate,
                  Double salary) {
           this.name = name;
           this.salary = salary;
           this.salaryDate = salaryDate;
       }
       public String getName() {
           return name;
       }
       public void setName(String name) {
          this.name = name;
       }
    
       public Date getDate() {
          return salaryDate;
       }
       public void setDate(Date date) {
          this.salaryDate = date;
       }
       public void setSalary(Double salary) {
          this.salary = salary;
       }
       public Double getSalary() {
           return salary;
       }
    }
    
    public class Employee {
       public static void main(String args()) {
          EmployeeMut emp = new EmployeeMut("Emp1",
                          new Date(), 1000.00);
          emp.setName("Emp2");
          emp.setSalary(2000.00);
          emp.setDate(new Date());
       }
    }
    

    Dies ist ein Beispiel für ein veränderliches Objekt. Schauen wir uns die Regeln an, die befolgt werden müssen, um dies in ein unveränderliches Objekt umzuwandeln.

    Wie implementiert man ein unveränderliches Objekt?

    • 1. Vermeiden Sie es, Setter-Methoden/andere Methoden offenzulegen, die es Clients ermöglichen könnten, die Werte der Instanzvariablen des Objekts zu ändern. In unserer Beispielklasse ist zu sehen, dass die Clients in der Lage sind, die Werte nach der Objekterstellung erfolgreich zu ändern, wie durch emp.setName(„Emp2“) gezeigt, obwohl es ursprünglich auf „Emp1“ initialisiert wurde, was bedeutet, dass sich der Wert von seinem Wert geändert hat Anfangszustand von Emp1. Vermeiden Sie es daher, solche Methoden den Clients zugänglich zu machen.
    • 2. Machen Sie alle Felder privat und endgültig.
    import java.util.Date;
    class EmployeeMut {
    private final String name;
       private final Date salaryDate;
       private final Double salary;
    public EmployeeMut(String name, Date salaryDate,
                  Double salary) {
          this.name = name;
          this.salary = salary;
          this.salaryDate = salaryDate;
       }
       public String getName() {
          return name;
       }
       public Date getDate() {
          return salaryDate;
       }
       public Double getSalary() {
          return salary;
       }
    
    }
    
    public class Employee {
        public static void main(String args()) {
          EmployeeMut emp = new EmployeeMut("Emp1",
                            new Date(), 1000.00);
        }
    }
    

    Aus meinem ersten Beispiel habe ich gerade alle Verweise auf die Setter-/Modify-Methoden entfernt und außerdem alle Felder privat und final gemacht.

    • 3. Unsere Klasse „EmployeeMut“ ist öffentlich und kann daher von jedem Client erweitert werden. Wenn eine untergeordnete Klasse es erweitert, besteht die Möglichkeit, dass die untergeordnete Klasse einige Verhaltensweisen/Felder der übergeordneten Klasse ändert. Um dies zu vermeiden, legen Sie die Klasse als final fest.
    final class EmployeeMut {
    
     private final String name;
     private final Date salaryDate;
     private final Double salary;
    

    Was wäre, wenn wir nicht wollten, dass der Unterricht endgültig ist? Es gibt eine alternative Technik als die Verwendung der „finalen“ Klasse, nämlich die Verwendung privater Konstruktoren und statischer Factory-Methoden um diese privaten Konstruktoren aufzurufen. Die Klasse wird öffentlich sein, aber da Konstruktoren privat sind, wird niemand direkt darauf zugreifen.

    class EmployeeMut {
    
        private String name;
        private Date salaryDate;
        private Double salary;
    
        private EmployeeMut(String name,
                                     Date salaryDate,
                Double salary) {
         this.name = name;
         this.salary = salary;
         this.salaryDate = new Date
                                         (salaryDate.getTime());
        }
    
        public static EmployeeMut valueOf(String name,
                  Date salaryDate,
                                     Double salary) {
         return new EmployeeMut(name, salaryDate,
                                             salary);
        }
    }
    

    Beim Vergleich mit dem vorherigen Code habe ich gerade das Schlüsselwort final entfernt und einen privaten Konstruktor mit einer statischen Methode hinzugefügt, die diesen Konstruktor aufruft. Tatsächlich wird Java-Entwicklern von Experten empfohlen, den Aufruf statischer Fabriken anstelle von Konstruktoren zu verwenden. Versuchen wir also von nun an, statische Fabriken zu verwenden, um neue Objekte aus Client-Programmen zu erstellen, anstatt Konstruktoren zu verwenden. So erstellt der Client ein neues Objekt.

    Employee mtu = EmployeeMut.valueOf("name", new Date(), 1000.00);
    

    Haben wir alles getan, um die Klasse unveränderlich zu machen? Gibt es etwas, das der Kunde ändern kann? Ja. Betrachten Sie das folgende Clientprogramm.

    public class Employee {
    
       public static void main(String args()) {
            Date date = new Date();
            EmployeeMut emp = new EmployeeMut("Emp1", date,
                           1000.00);
            System.out.println("Month : " +
                                     emp.getDate().getMonth());
            date.setMonth(date.getMonth() + 5);
            System.out.println("Month : " +
                                      emp.getDate().getMonth());
       }
    }
    

    Wie man sieht, können Kunden die „Datums“-Referenz ändern, die in der Emp-Referenz verwendet wurde. Dies verstößt gegen die Regeln der Unveränderlichkeit. Der Grund dafür ist, dass java.util.Date ein veränderbares Objekt ist, wenn wir Java-Dokumente überprüfen. Müssen wir also java.util.Date unveränderlich machen, um unsere Beispielklasse unveränderlich zu machen? Nein. Es gibt einen anderen Weg, und das ist die 4. Regel.

    • 4. Wenn unsere Klasse Felder hat, die auf veränderliche Objekte verweisen, sollten wir defensive Kopien dieser Referenzen erstellen, bevor wir sie an den Client übergeben. Auf diese Weise verweist die Referenz immer auf ein neues Objekt, wenn auf eine der Methoden zugegriffen wird, die dieses veränderliche Objekt enthalten. Dies muss sorgfältig überprüft werden, auch wenn uns eine einzige Stelle fehlt, selbst dann ist die Klasse nicht veränderbar.
    public EmployeeMut(String name, Date salaryDate,
                        Double salary)
    {
     this.name = name;
     this.salary = salary;
     this.salaryDate = new Date(salaryDate.getTime());
    
         public Date getDate() {
            return new Date(this.salaryDate.getTime());
         }
    

    Um die vierte Regel zu umgehen, habe ich den Code so geändert, dass er immer ein neues Objekt zurückgibt, wenn das Date-Objekt verwendet wird. Dadurch wird sichergestellt, dass immer neue Date()-Instanzen zurückgegeben werden, sodass die ursprüngliche Date()-Instanz unverändert bleibt. Führen Sie nach dieser Änderung denselben Beispiel-Clientcode aus und Sie werden überrascht sein, die gleiche Ausgabe zu sehen, da wir auch das Date-Referenzobjekt unveränderlich gemacht haben. Jetzt ist unsere EmployeeMut-Klasse unveränderlich.

    Nachdem wir die Regeln für die Unveränderlichkeit von Objekten kennengelernt haben, schauen wir uns nun einige der Vorteile an, die diese unveränderlichen Objekte für Java-Entwickler haben.

    Vorteile unveränderlicher Objekte

    1. Thread-Sicherheit und daher keine Synchronisierungsprobleme.

    2. Wir müssen keinen Klon- oder Kopierkonstruktor implementieren. Warum? Da der Klon oder die Kopie dasselbe Objekt wie das Original erzeugt, macht es keinen Sinn, sie in unveränderlichen Objekten zu verwenden. Ich werde versuchen, copy() und clone() in einem separaten Artikel zu behandeln.

    3. Eine weitere häufig verwendete Technik besteht darin, häufig verwendete Instanzen in unveränderlichen Objekten zwischenzuspeichern. Jeder Aufruf dieser häufig genutzten Instanzen nimmt nicht viel Zeit in Anspruch. Z.B.,

    Entnommen aus dem Open-Source-Code von BigDecimal
    // Cache gängiger kleiner BigDecimal-Werte

    new BigDecimal(BigInteger.ZERO, 0, 0);
      new BigDecimal(BigInteger.ONE, 1, 0);
      new BigDecimal(BigInteger.TEN, 10, 0);
    

    Das Client-Programm würde also diese bereits erstellten Instanzen verwenden, anstatt selbst neue Objekte neu zu erstellen.

    BigDecimal big = BigDecimal.TEN;
       System.out.println(big.toString());
    

    4. Außerdem hilft diese unveränderliche Eigenschaft, indem sie die zwischengespeicherten Ergebnisse speichert. Angenommen, wir haben eine Methode, die basierend auf einer Instanzvariablenberechnung immer einen eindeutigen Wert zurückgeben sollte. Wir wissen jedoch, dass Aufrufe dieser Methode mit demselben Objekt denselben Wert zurückgeben sollten, da unsere Objekte jetzt unveränderlich sind. Schauen Sie sich das folgende Beispiel an, in dem eine solche Technik verwendet wird:

    private int hash ; // cached result .. by default, hash = 0
    
        public int hashCode() {
            int h = hash;
            if (h ==0 && value.length > 0) {
               /* calculate hash value using some technique
               .....
               ......
               */
                  hash = h;
            }
            return h;
        }
    

    Um diese Technik anzuwenden, müssen wir die Regel lockern, dass alle Felder endgültig gemacht werden sollten, und das ist hier kein großes Problem, da das Objekt weiterhin unveränderlich bleibt. Wir haben eine neue Variable „hash“ und haben sie hier nicht endgültig gemacht, um die zwischengespeicherten Ergebnisse bei nachfolgenden Aufrufen mit demselben „EmployeeMut“-Objekt zu speichern. Nur beim ersten Mal wird der Hash-Wert berechnet, da der Hash-Wert nur zu Beginn Null ist und von da an das zuvor berechnete „h“ zwischengespeicherte Variablenergebnis an den Aufrufer zurückgegeben wird. Dadurch entfallen Berechnungen, die jedes Mal durchgeführt werden müssen, selbst für gleiche Objektaufrufe, obwohl wir wissen, dass der zurückgegebene Wert derselbe sein sollte. Das liegt daran, dass wir wissen, dass unser Objekt unveränderlich ist, das heißt, sein Wert ändert sich nicht und daher werden Berechnungen, selbst wenn sie in unserem Fall auf Instanzvariablen basieren, ähnliche Werte für denselben Objektaufruf zurückgeben.

    Zusammenfassung

    Bisher war alles gut. Dann stellt sich die Frage, warum nicht alle Entwickler nur unveränderliche Objekte verwenden. Laut Java-Experten besteht einer der Nachteile unveränderlicher Objekte darin, dass für jeden einzelnen Wert separate neue Objekte erstellt werden müssen (denken Sie an die 4. Regel!!!!!). Wir verbrauchen also etwas zusätzlichen Speicher, der bei veränderlichen Objekten nicht genutzt wird. Aber diese einzige Sorge ist kein triftiger Grund für Entwickler, sich für veränderbare Objekte zu entscheiden. Java-Experten raten dazu, immer veränderliche Objekte zu verwenden und, wenn dies nicht möglich ist, zu versuchen, die Unveränderlichkeit dieses Objekts so weit wie möglich zu bewahren.

    Das ist alles über unveränderliche Java-Objekte für heute. Ich hoffe, die Lektüre hat Ihnen gefallen. Geben Sie gegebenenfalls Ihre Kommentare/Fragen weiter.

    Dieser Artikel wurde ursprünglich unter veröffentlicht Java-Tutorials – Lasst uns ins Meer springenhier mit Genehmigung des Autors und als Teil des JBC-Programms erneut veröffentlicht.

    Kommentar verfassen

    Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

    Nach oben scrollen