Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
CONTACT 
Immutability - Part 2

Immutability - Part 2
Unveränderliche Typen in Java
Teil 2: Wie die  Immutability-Adaptoren im JDK-Collection-Framework funktionieren und was das Schlüsselwort final mit Immutability zu tun hat.

JavaSPEKTRUM, Juli 2003
Klaus Kreft & Angelika Langer

Dies ist das Manuskript eines Artikels, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im JavaSPEKTRUM erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

 
 

Im letzen Artikel dieser Kolumne (siehe / KRE1 /) haben wir diskutiert, wie man unveränderliche Typen, duale Klassen und Read-Only-Adaptoren implementieren kann. Das wollen wir in diesem Artikel weiter verfolgen.  Es gibt Beispiele für  Read-Only-Adaptoren im JDK, nämlich im Collection-Framework.  Diese "unmodifiable" Collections sehen wir uns in diesem Artikel näher an. Außerdem erklären wir, was das Schlüsselwort final mit Immutability zu tun hat. Und am Ende fassen wir zusammen, welche Arten von Immutability es gibt, was sie bedeuten und wie man damit umgeht.
 
 

Immutability-Adaptoren für Collections im JDK

Im Package java.util (siehe / JDK /) ist u.a. der Collection Framework definiert mit verschiedenen Container-Interfaces wie Set, List, und Map sowie verschiedenen Container-Implementierungen wie ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, usw. Die Collections sind erwartungsgemäß veränderliche Typen; sie haben z.B. Methoden wie add() und remove() für das Hinzufügen und Löschen von Elementen.

Zu diesen Collection-Typen sind Immutability-Adaptoren definiert, die man sich über statische Methoden der Klasse Collections beschaffen kann:  man übergibt eine Referenz auf eine veränderliche Collection als Argument und erhält als Return eine Referenz auf eine unveränderliche Collection zurück. Genauer gesagt bekommt man keine Referenz auf eine unveränderliche Collection, sondern eine Referenz auf ein Objekt, das eine eingeschränkte Sicht auf die veränderliche Collection gibt, nämlich eine nur lesende Sicht. Wir werden im folgenden aber dennoch den Begriff "unveränderliche Collection" anstelle von "Lese-Sicht auf veränderliche Collection" verwenden, weil es einfach kürzer ist, und außerdem ist es die direkte Übersetzung des entsprechenden Begriffs "unmodifiable collection", wie er in der JDK-API-Dokumentation verwendet wird.

Nehmen wir das Beipiel einer ArrayList. Zu einem ArrayList-Objekt kann man sich über den Aufruf von Collections.unmodfiableList() die Lese-Sicht auf das ArrayList-Objekt geben lassen.

List myList;
myList = new ArrayList();
myList = Collections.unmodifiableList(myList);

Von welchem Typ die Lese-Sicht auf das ArrayList-Objekt ist, bleibt unbekannt, weil die Methode Collections.unmodfiableList()  lediglich eine Referenz vom Typ List zurückgibt. List ist ein Interface, das sowohl von ArrayList als auch von LinkedList und Vector implementiert wird. Das bedeutet, dass der "unveränderliche" Container die gesamte List-Funktionalität anbietet, was einigermaßen überraschend ist.  Man würde vielmehr erwarten, dass modifizierende Methoden wie add() und remove() gar nicht erst zur Verfügung stehen.  Die Designer des JDK-Collection-Frameworks haben aber eine andere Lösung gewählt: die verändernden Methoden stehen zur Verfügung und können auch aufgerufen werden, scheitern aber zur Laufzeit mit einer  UnsupportedOperationException.   Hier wird der Compilezeit-Check durch einen Laufzeit-Check ersetzt.
 

Immutability-Adaptoren mit Laufzeit-Check

Übertragen wir diese im JDK verwendete Technik einmal auf die Implementierung, die wir in der  letzten  Ausgabe dieser Kolumne (siehe / KRE1 /) diskutiert haben. Wir hatten einen unveränderlichen Typ ImmutableStamp implementiert, der in einer Widget-Klasse verwendet wurde, um eine lesende Zugriffsmethode getLastModification() zu implementieren.  Hier noch einmal zur Erinnerung die  Widget-Klasse sowie die Stamp- und ImmutableStamp-Klasse:

public final class Stamp
{
  private Date date;
  private String author;

  ... other fields, constructors and methods ...

  public Date getDate()
  {
    return Date.clone();
  }
  public void setDate(Date d)
  {
    date = d.clone();
  }
  public String getAuthor()
  {
    return author;
  }
  public void setAuthor(String a)
  {
    author = a;
  }
}

public final class ImmutableStamp
{
  private Stamp stamp;

  public ImmutableStamp(Stamp s) { stamp = s.clone(); }

  public Date getDate()
  {
    return stamp.getDate();
  }
  public String getAuthor()
  {
    return stamp.getAuthor();
  }
}

public final class Widget
{
  private Stamp lastModification;

  ... other methods and fields ...

  public ImmutableStamp getLastModification()
  {
    return new ImmutableStamp(lastModification);
  }
}

Die Stamp-Abstraktion hatten wir als duale Klasse implementiert, die aus einer veränderlichen Klasse Stamp und einer unveränderlichen Klasse  ImmutableStamp besteht. Man beachte, dass die  ImmutableStamp-Klasse in unserer Implementierung ausschließlich lesende Methoden hat.

Wenn wir die JDK-Technik, die für die "unmodifiable collections" verwendet wird, auf unsere ImmutableStamp-Klasse übertragen, dann sieht die Klasse anders aus, nämlich so:

public final class ImmutableStamp
{
  private Stamp stamp;

  public ImmutableStamp(Stamp s) { stamp = s; }

  public Date getDate()
  {
    return stamp.getDate().clone();
  }
  public void setDate(Date d)
  {
    throw new UnsupportedOperationException();
  }
  public String getAuthor()
  {
    return stamp.getAuthor();
  }
  public void setAuthor(String s)
  {
    throw new UnsupportedOperationException();
  }
}

Ob man dieses Vorgehen für sinnvoll hält oder nicht, muss jeder für sich selbst entscheiden. Wir halten Immutability-Adaptoren, bei denen die modifizierenden Methoden zwar angeboten werden, aber immer und unbedingt eine Exception werfen, für wenig nachahmenswert und würden die verändernden Methoden lieber gleich weglassen.

Dass die JDK-Technik mit dem Laufzeit-Check nicht ganz so ideal ist, zeigt sich auch in der Dokumentation der Immutability-Adaptoren des JDK-Collection-Frameworks (siehe / JDK /). Wenn man die JavaDoc liest, ist eigentlich nicht auf Anhieb klar, welche Methoden ein "unmodifiable" Container nun unterstützt und welche nicht. Wenn man dagegen ein Interface hätte, in dem nur die lesenden Methoden vorhanden wären, dann könnte man sofort erkennen, welche Methoden von der "unmodifiable" Collection unterstützt werden und welche nicht.  Und natürlich würde auch der Compiler bereits beim Übersetzen Fehler melden, wenn man eine Methode aufrufen will, die nicht erlaubt ist.

Die JDK-Designer haben aber für die Entscheidung gegen den Compiletime-Check einen guten Grund gehabt. Als Begründung findet man die Aussage, dass sie andernfalls  unzählige Interfaces und Klasses hätten definieren müssen, was nun auch nicht gerade zur Klarheit beigetragen hätte (siehe / FAQ /).  Das stimmt auch, weil es nicht nur den "unmodifiable" Adaptor gibt, sondern auch noch einen "synchronized" Adaptor, und man kann beide Adaptionen beliebig miteinander kombinieren. Dann hätte man gleich 3 Interfaces für jeden der 6 Collection-Typen definieren müssen. Statt der erforderlichen 18 Interfaces hat man gar nichts Neues definieren müssen; die unveränderlichen Listen sind immer noch Listen, weil sie alle Methoden des List-Interfaces unterstützen, obwohl sie einige Methoden eigentlich konzeptionell nicht unterstützen.
 

"Shallow" Immutability

Sehen wir uns einmal an, was man mit den  Immutability-Adaptoren des JDK anfangen kann und wie sie benutzt werden. Als Beispiel verwenden wir wieder unsere Widget-Klasse.

Wenn die Klasse Widget nicht nur die letzte Änderung speichern würde, sondern die gesamte Historie aufzeichnen würde, dann müßte sie intern eine Collection (z.B. eine Liste) von Stamp-Objekten verwalten. Die Klasse Widget könnte dann so aussehen:

public final class Widget
{
  private List changeLog;

  ... other methods and fields ...

  public ImmutableStamp getLastModification();
  public List getChangeLog();
}

Dabei nehmen wir an, dass in der privaten Liste Stamp-Objekte abgelegt werden und dass die Stamp-Abstraktion wie im letzten Artikel empfohlen als duale Klasse bestehend aus 2 Klassen, ImmutableStamp und Stamp, implementiert ist. Wie müßten die beiden Zugriffsmethoden getLastModification() und getChangeLog() implementiert werden?

Fangen wir mit der Methode getLastModification() an. Sie soll das letzte Element in der Liste der Stamp-Objekte als ImmutableStamp zurückgeben.  Da muss man sich zunächst einmal fragen: was genau soll das bedeuten?  Es gibt zwei Möglichkeiten: entweder es wird ein Snapshot dieses letzten Eintrags erzeugt und zurück gegeben oder es wird eine Read-Only-Sicht auf den letzten Eintrag zurück gegeben.   Beides würde Sinn machen.  Die Read-Only-Sicht wäre eine reine Sicht auf einen bestimmten Eintrag in der Stamp-Liste.  Wenn sich dieser Eintrag ändert, dann würde man das sehen können.  Der Snapshot dagegen ist unveränderlich. Er ist eine Kopie des  Eintrags in der  Stamp-Liste, so wie er zum Zeitpunkt des Aufrufs der  Methode getLastModification() gerade eingetragen ist.

Hier ist die Implementierung der Methode getLastModification(), wenn sie eine Read-Only-Sicht zurückliefert:

public final class Widget
{
  private List changeLog;

  ... other methods and fields ...

  public ImmutableStamp getLastModification()
  {
    return changeLog.get(changeLog.size()-1);
  }
  public List getChangeLog();
}

Hier ist die Implementierung der Methode getLastModification(), wenn sie einen Snapshot zurückliefert:

public final class Widget
{
  private List changeLog;

  ... other methods and fields ...

  public ImmutableStamp getLastModification()
  {
    return new ImmutableStamp(changeLog.get(changeLog.size()-1));
  }
  public List getChangeLog();
}

Etwas komplizierter ist die Lage bei der Methode getChangeLog(). Auch hier muss man sich zunächst fragen: was soll denn genau die Semantik dieser Methode sein?  Eines ist klar: sie soll keinen Schreibzugriff auf die private Stamp-Liste des  Widget-Objekts zulassen. Das heißt, eine naive Implementierung wie die folgende wäre auf jeden Fall falsch:

public List getChangeLog()
  {
    return changeLog;  // don't do this !!!
  }

Hier wird voller (d.h. auch schreibender) Zugriff auf die private Liste gibt. Aber wie wäre es damit?

  public List getChangeLog()
  {
    return Collections.unmodifiableList(changeLog);
  }

Hier wird der Immutability-Adaptor aus dem Collection-Framework verwendet, um eine unveränderliche Sicht auf die private Liste zurück zu geben. Das sieht so aus, als hätten wir damit eine Zugriffsmethode implementiert, die eine Read-Only-Sicht auf ein privates Feld eines Objekts gibt.  Über eine Read-Only-Sicht kann man üblicherweise das Feld nicht ändern, aber man kann Veränderungen, die von anderen gemacht werden, sehen. Das wäre eine sinnvolle Semantik für eine solche Zugriffsmethode.  Bleibt die Frage, haben wir tatsächlich eine Read-Only-Sicht implementiert?

Wenn man genauer hinsieht, stellt man fest, dass zwar Methoden wie add() und remove() auf der zurückgelieferten "unmodifiable" Liste nicht mehr funktionieren, genauer gesagt, sie werfen grundsätzlich eine UnsupportedOperationException. Das heißt, wir haben eine Read-Only-Sicht auf die Collection. Aber Methoden wie get() oder der Zugriff auf Elemente über den Iterator der Collection geben nach wie vor vollen Zugriff auf die enthaltenen Elemente.  Die Elemente in der Liste sind Stamp-Objekte, die man dann natürlich nach Belieben modifizieren kann, wie das folgende Beispiel zeigt:

Widet w = new Widget();
...
List log = w.getChangeLog();
Iterator iter = log.iterator();
while (iter.hasNext())
{
  Stamp s = (Stamp)iter.next();
  s.setDate(new Date());
  s.setAuthor("Molly Malicious");
}

Das heißt, wir haben keine Read-Only-Sicht implementiert. Das Beispiel zeigt, dass der Immutability-Adaptor der Collection allein nicht ausreicht, um Veränderungen an den privaten Daten des Widget zu verhindern.  Es genügt nicht, dass wir eine Read-Only-Sicht auf den Container zurück geben. Wir müssten außerdem eine Read-Only-Sicht auf die im Container enthaltenen Element geben.

Das könnten wir in diesem speziellen Beispiel dadurch erreichen, dass wir die gesamte Implementierung der Widget-Klasse umkrempeln und in der Liste nicht Stamp-Objekte, sondern ImmutableStamp-Objekte abgelegen. Das wäre durchaus eine Überlegung wert. Aber diese Lösungsmöglichkeit besteht ja nicht immer.  Das geht beispielsweise dann nicht, wenn die Listenelemente nicht als duale Klassen implementiert sind und es den unveränderlichen Typ gar nicht gibt, oder wenn die Collection von der Semantik her tatsächlich veränderliche Element enthalten soll, oder wenn man eine existierende Klasse wie die Widget-Klasse nicht in größerem Stil ändern will.

In solchen Fällen bleibt einem nichts anderes übrig, als eine Kopie des gesamten Containers zu erzeugen.  In unserem Beipiel sähe eine Implementierung mit Kopieren des Containers dann so aus:

  public List getChangeLog()
  {
    List copy = new ArrayList();
    Iterator iter = log.iterator();
    while (iter.hasNext())
    {
       copy.add(new ImmutableStamp(iter.next()));
    }
    return Collections.unmodifiableList(copy);
  }

Jetzt haben wir die Methode getChangeLog() tüchtig umgekrempelt. Sie hat nicht mehr die Semantik, dass sie eine Read-Only-Sicht auf die private Liste der Stamp-Objekte gibt, sondern sie liefert jetzt eine Read-Only-Sicht auf einen Snapshot dieser privaten Liste.

Es sind andere Implementierungen denkbar, zum Beispiel eine Read-Only-Sicht auf eine Kopie einer private Collection, die Read-Only-Sichten auf die Elemente der Original-Collection enthält.  Das wäre dann eine echte Read-Only-Sicht.  In unserer Lösung geht das nicht, weil unsere duale Stamp-Abstraktion keine Read-Only-Sicht auf veränderliche Stamp-Objekte ermöglicht. In anderen Fällen wäre das aber eine sinnvolle Alternative.

Fazit: Wie schon beim Klonen (siehe / KRE2 /) muss man auch bei der Immutability beachten, dass man die Unveränderlichkeit "tief genug" ansetzt.  Die Read-Only-Sicht auf einen Container, der veränderliche Objekte enthält, genügt nicht, um sicher zu stellen, dass die Collection auch tatsächlich unverändert bleibt. Man muss außerdem noch dafür sorgen, dass auch die enthaltenen Element unveränderlich sind.  Wobei man unterscheiden muss, ob man mit "unveränderlich" eine Read-Only-Sicht oder einen Snapshot meint.  Für eine Read-Only-Sicht auf einen Container braucht man eine "unmodifiable collection", die Read-Only-Sichten auf die Element enthält. Für einen Snapshot eines Containers braucht man eine Kopie des Containers, der Kopien der Elemente enthält.
 

final Variablen

Nach all unseren Betrachtungen über unveränderliche Typen und Read-Only-Sichten auf veränderliche Objekte fragt man sich, welche Unterstützung die Sprache Java eigentlich in diesem Zusammenhang bietet.  Wie man an den Beispielen in diesem und dem vorangegangenen Artikel schon gesehen hat, bietet Java  keinerlei Möglichkeiten, lesende Methoden von schreibenden zu unterscheiden, oder um veränderliche von unveränderlichen Typen zu unterscheiden, oder um zu sagen, ob eine Methode eine Referenz auf eine Kopie oder  aufs Original liefert. All das muss man verbal in der Dokumentation aufschreiben und auch dort wieder nachlesen. Es gibt lediglich die Möglichkeit,   veränderliche von unveränderlichen Variablen zu unterscheiden.  Dazu kann man das Schlüsselwort final auf Variablen anwenden.
Der Inhalt einer Variablen, die als final erklärt ist, kann nicht geändert werden. Bei Variablen vom primitivem Typ, d.h. int, long, boolean, etc., bedeutet es, dass sich der Wert der Variablen nach der Initialisierung nicht mehr ändert.  Bei Referenzvariablen bedeutet es, dass sich die in der Referenzvariablen gespeicherte Adresse nicht mehr ändert.  Mit anderen Wort, eine final Referenzvariable verweist auf das Objekt, das ihr bei der Initialisierung zugewiesen wurde und kann niemals auf ein anderes Objekt verweisen. Es bedeutet aber nicht, dass das referenzierte Objekt vor Veränderungen geschützt ist.
Beispiele:

final int max = 256;
max = 0; // error: does not compile

final Date deadline = new Date();
deadline = new Date(100,0,1,0,0,0);     // error: does not compile
deadline.set(new Date(100,0,1,0,0,0));  // fine:  compiles

Mit anderen Worten, das Schlüsselwort final hilft im Zusammenhang mit Immutability nur in wenigen Situationen.  Beispielsweise hilft es, wenn man einen unveränderlichen Typ bauen will, dessen Felder alle von primitivem Typ sind. Dann kann man alle Felder als final deklarieren und der Compiler kann dann dafür sorgen, dass man nicht versehentlich in irgendeiner Methode diese Felder verändert.  Hier ist ein Beispiel:

public final class ImmutablePoint
{
  private final float x;
  private final float y;
  private final float z;

  ImmuablePoint(float a, float b, float c) { x=a; y=b; z=c; }

  ... other methods and fields ...

  public float getX() { return x; }
  public float getY() { return y; }
  public float getZ() { return z; }

  public void translate(ImmutablePoint p) // nonsensical method
  {
    x+=p.getX();  // error: does not compile
    y+=p.getY();  // error: does not compile
    z+=p.getZ();  // error: does not compile
  }
}

Wenn man aber Felder hat, die Referenzen sind, dann hilft es nicht, wenn man diese Felder als final deklariert, weil es ja nicht genügt, dass die Referenzbeziehung unveränderlich ist; man will eigentlich sagen, dass auch das referenzierte Objekt nicht geändert werden soll.  Das kann man aber mit Sprachmitteln in Java gar nicht ausdrücken. Hier ist ein weiteres Beipiel, in dem man die fehlende Unterstützung bei final Referenzvariablen sieht. Wir werden daran zeigen, wie man diese Lücke mit Hilfe von unveränderlichen Typen schließen kann. Betrachten wir folgende Klasse und ihre  final Felder:

public final class ImmutablePeriod
{
  private final Date begin;
  private final Date end;

  ImmuablePeriod(Date a, Date z) { begin=a; end=z; }

  ... other methods and fields ...

  public Date getBegin() { return begin; }
  public Date getEnd()   { return end; }

  public void stretch(int factor) // nonsensical method
  {
    end.setTime(end.getTime()*factor);  // oops!
  }
}

Man kann versehentlich die referenzierten Date-Objekte ändern, ohne dass der Compiler sich beschwert. Da die Sprache keine Sprachmittel bietet, um das zu verhindern, kann nur mit Immutability-Interfaces oder unveränderlichen Typen sagen, was man meint. Wenn man z.B. eine ImmutableDate-Adaptor-Klasse hat, dann kann man es so machen:

public final class ImmutableDate
{
  private Date date;

  public ImmutableDate(Date d) { date=d; }
  public long getTime()  { return date.getTime(); }
  public int getYear()   { return date.getYear(); }
  public int getMonth()  { return date.getDate(); }
  public int getDay()    { return date.getDay();
  public int getHour()   { return date.getHour(); }
  public int getMinute() { return date.getMinute(); }
  public int getSecond() { return date.getSecond();
}

public final class ImmutablePeriod
{
  private final ImmutableDate begin;
  private final ImmutableDate end;

  public ImmuablePeriod(Date a, Date z)
  {
    begin=new ImmutableDate(a);
    end=new ImmutableDate(z);
  }

  ... other methods and fields ...

  public ImmutableDate getBegin() { return begin; }
  public ImmutableDate getEnd()   { return en; }

  public void stretch(int factor)
  {
    end.setTime(end.getTime()*factor);  // error: does not compile
  }

Man merke sich, dass final Referenzvariablen, im Gegensatz zu final Variablen von primitivem Typ, nicht dazu führen, dass das referenzierte Objekt unverändert bleibt. Es kommt in der Praxis selten vor, dass man sagen will, dass die Referenzbeziehung zwischen Variable und Objekt unveränderlich ist. Viel häufiger möchte man sagen, dass die Referenzvariable und das referenzierte Objekt unveränderlich ist, aber das kann man  in Java mit dem Sprachmittel final  nicht ausdrücken.

Zur Vollständigkeit eine letzte Bemerkung zum Schlüsselwort final: Die Anwendung des Schlüsselworts  final  auf Klassen oder Methoden hat natürlich gar nichts mit Immutability von Variablen und Objekten zu tun, sondern hat Auswirkungen auf die Vererbung. Von final Klassen kann man nicht ableiten und  final Methoden kann man nicht überschreiben.
 

Immutability – eine zusammenfassende Begriffsbestimmung

Wir haben uns in diesem und dem vorangegangenen Artikel ausführlich mit diversen Aspekten von unveränderlichen Typen befasst. Dabei haben wir verschiedene Arten von Immutability gesehen. Fassen wir abschließen noch einmal zusammen, was man eigentlich unter "unveränderlich" versteht:
  • Read-Only-Sicht.

  •  

     
     
     

    Bei dieser Art von Immutability definiert man Interfaces oder Klassen, die ausschließlich lesende Methoden haben.  Variablen von diesen Typen können auf veränderliche oder unveränderliche Objekte von Subtypen verweisen.

    Beispiele: Immutability-Interfaces oder auch die "unmodifiable" Adaptoren der JDK-Collections oder der Supertyp einer dualen Klasse wie in / KRE1 / beschrieben.

    Diese Art der Immutability kann leicht missverstanden werden.  Eine Read-Only-Sicht bedeutet lediglich, dass man ohne Cast das referenzierte Objekt nicht ändern kann. Eine Read-Only-Sicht ist aber keine Garantie, dass das referenzierte Objekt tatsächlich unverändert bleibt. Es kann andere Referenzen auf dasselbe Objekt geben, über die das Objekt verändert werden kann. Man kann auch jederzeit einen Cast auf den "echten" Typ des Objekts machen. Falls der "echte" Typ Veränderungen zuläßt, dann kann man das referenzierte Objekt sogar selber verändern.
     

  • "Echte" Unveränderlichkeit.

  •  

     
     
     

    Bei dieser Art von  Immutability hat man ebenfalls Klassen, die ausschließlich lesende Methoden haben. Aber diesmal können Variablen von diesen Typen  nur auf unveränderliche Objekte verweisen. Typischerweise sind solche Klassen als final deklariert, d.h. es gibt gar keine Subklassen.

    Beispiel: final Klassen, die nur lesende Methoden haben, z.B. String, oder der unveränderliche Teil einer dualen Klasse (siehe / KRE1 /).

    Das ist die Art von Unveränderlichkeit, bei der man das Object Sharing bedenkenlos zulassen kann, weil das Objekt über keine Referenz jemals geändert werden kann. Man braucht Objekte diesen Typs nicht zu kopieren und man kann in Multithread-Programmen auf die Synchronisation der Zugriffe auf Objekte diesen Typs verzichten.
     

  • final Variable von primitivem Typ.

  •  

     
     
     

    Solche Variablen sind ebenfalls "echt" unveränderlich.

    Das ist der einzige Fall, wo man mit Mitteln der Sprache, nämlich über die Qualifizierung mit final, ein Objekt als unveränderlich kenzeichnen kann.
     

  • final Referenzvariable.

  •  

     
     
     

    Die Art von Immutability bringt man zum Ausdruck durch die Kennzeichnung einer Referenzvariablen als final Variable.

    Es bedeutet, dass eine unveränderliche Beziehung zwischen Referenzvariable und Objekt besteht.  Es bedeutet aber nicht, dass das referenzierte Objekt unveränderlich ist.
     

Tipps für den Umgang mit unveränderlichen Typen in der Praxis

Benutzung

Wenn man unveränderliche Typen verwendet, dann muss man sich immer genau überlegen, ob man einen echt unveränderlichen Typ oder nur eine Read-Only-Sicht auf etwas möglicherweise Veränderliches vor sich hat. Beides kann sinnvoll und nützlich sein, je nach Kontext. Man darf diese beiden Arten von Immutability nur nicht miteinander verwechseln.

Die Read-Only-Sicht sagt, dass man in diesem Kontext keine Veränderungen des referenzierten Objekts machen will.  Eine Garantie ist das allerdings nicht. Nur die echt unveränderlichen Typen  garantieren, dass die referenzierten Objekte sich nicht ändern.  Und nur Objekte, die sich tatsächlich nicht ändern, kann man bedenkenlos als Shared Objects verwenden.
 

Implementierung

Wenn man unveränderliche Typen implementiert, muss man sich ebenfalls überlegen, was man eigentlich mit "unveränderlich" meint.  Man muss es sorgfältig in der Dokumentation beschreiben und man muss es entsprechend implementieren.  Insbesondere muss man wissen, wie man lesende Zugriffsfunktionen implementiert; das haben wir im vorangegangenen Artikel (siehe /KRE/) besprochen.

Das sicherste Beispiel einer Abstraktion, die alle Formen der Immuability unterstützt, ist die duale Klasse, wie wir sie in  im vorangegangenen Artikel (siehe / KRE1 /) beschrieben und in vereinfachter Form (siehe Stamp-Abstraktion) in diesem Artikel verwendet haben.  Sie besteht aus einer Superklasse oder einem Super-Interface, dass die Read-Only-Sicht repräsentiert, und zwei final Subklassen, die den veränderlichen und den echt unveränderlichen Typ implementieren. Über Konstruktoren und Casts kann zwischen den verschiedenen Typen hin und her transformiert werden. Ob man allerdings für eine Abstraktion den Implementierungsaufwand einer dualen Klassen spendieren will, muss man im Einzelfall entscheiden. Das wird man sicher nicht für alle Abstraktionen tun.
 

Literaturverweise

 
/KRE1/ Unveränderliche Typen in Java (Teil 1)
Klaus Kreft & Angelika Langer
JavaSpektrum, März 2003
http://www.AngelikaLanger.com/Articles/EffectiveJava/08.Immutability-Part1/08.Immutability-Part1.html
/KRE2/ Das Kopieren von Objekten in Java (Teil 1 - 3) 
Klaus Kreft & Angelika Langer
JavaSpektrum, September 2002 + November 2002 + Januar 2003
http://www.AngelikaLanger.com/Articles/EffectiveJava/05.Clone-Part1/05.Clone-Part1.html
http://www.AngelikaLanger.com/Articles/EffectiveJava/06.Clone-Part2/06.Clone-Part2.html
http://www.AngelikaLanger.com/Articles/EffectiveJava/07.Clone-Part3/07.Clone-Part3.html
/JDK/  Java 2 Platform Std. Ed. v1.4 - Package java.util
http://java.sun.com/j2se/1.4/docs/api/index.html
/FAQ/  Java Collections API Design FAQ
http://java.sun.com/j2se/1.3/docs/guide/collections/designfaq.html#1

 
 

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
 
Effective Java - Advanced Java Programming Idioms 
4 day seminar ( open enrollment and on-site)
 
  © Copyright 1995-2008 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/09.Immutability-Part2/09.Immutability-Part2.html  last update: 26 Nov 2008