Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | NEWSLETTER | 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 
NEWSLETTER 
CONTACT 
Immutability - Part 2

Immutability - Part 2
Unveränderliche Typen in Java
Teil 1: Wie implementiert man unveränderliche Typen, lesende Zugriffsmethoden, Immutability-Adaptoren und "duale Klassen"?

JavaSPEKTRUM, März 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 ).

 

Weil die Unveränderlichkeit (engl. immutability) von Typen ein wichtiges Thema in Java ist, widmen wir ihm diesen und den nächsten Artikel. Wir diskutieren die Implementierung von lesenden Methoden und die Eigenschaften von unveränderlichen Typen. In diesem Zusammenhang besprechen wir Read-Only-Adaptoren und sogenannte "duale Klassen". Im nächsten Artikel sehen wir uns die Immutability-Adaptoren in Collection-Framework des JDK an und diskutieren den Sinn und Zweck des Schlüsselworts final.
 
 

Wofür werden unveränderlichen Typen gebraucht?

Immutability spielt immer dann eine Rolle, wenn Objekte gemeinsam verwendet werden (engl. object sharing).  Diese Situation entsteht in Java, wenn Referenzen einander zugewiesen oder von und an Methoden übergeben werden.  Es verweisen dann mehrere Referenzen auf ein Objekt und die Besitzer dieser Referenzen können abwechselnd lesend und/oder schreibend auf das gemeinsam verwendete Objekt zugreifen.
Manchmal ist es unerwünscht, dass das gemeinsam verwendete Objekt von allen Beteiligten nach Belieben verändert werden kann. Dann kann man auf das Sharing verzichtet und Kopien anlegen, so dass jede Referenz auf ihre eigene Kopie des Objekts verweist.(Das Kopieren von Objekten haben wir ausführlich in / KRE1 / besprochen.) Es geht aber unter Umständen auch ohne das Kopieren. Den Aufwand für das Kopieren kann man vermeiden, wenn das gemeinsam verwendete Objekt gar nicht verändert werden kann. Und hier kommen unveränderliche Typen ins Spiel; sie helfen, den Performance-Overhead für das Kopieren von Objekten zu eliminieren.
Es gibt eine weitere Situation, in der unveränderliche Typen nützlich sind.  In Programmen mit mehreren parallelen Threads können Objekte gemeinsam von mehreren Threads verwendet werden. Dann gibt es ebenfalls mehrere Referenzen auf ein gemeinsam verwendetes Objekt.  Die Referenzen werden in verschiedenen parallel ablaufenden Threads gehalten und in solchen Fällen ist das Object Sharing ausdrücklich erwünscht: es soll eine Kommunikation zwischen den Threads über das gemeinsam verwendete Objekt stattfinden.

Wenn auf das gemeinsam verwendete Objekt schreibend zugegriffen werden kann, dann müssen die Zugriffe auf das gemeinsam verwendete Objekt synchronisiert werden, damit sie nacheinander und nicht ineinander verschränkt ablaufen. Sonst könnte es beispielsweise passieren, dass ein lesender Thread ein halb geschriebenes Objekt zu sehen bekommt, weil der schreibende Thread noch gar nicht fertig war, als er unterbrochen wurde.  Hier helfen unveränderlichen Typen, den Synchronisationsaufwand zu vermeiden. Wenn man weiß, dass das Objekt unveränderlich ist, dann kann man auf die Synchronisation gänzlich verzichten.

Im folgenden werden wir den Fall von Multithread-Anwendungen nicht weiter vertiefen, sondern wir werden den Nutzen von unveränderlichen Typen am Beispiel des Object Sharing in Single-Thread-Programmen  betrachten. Alle Techniken und Vor- und Nachteile, die wir diskutieren werden, gelten aber für die Nutzung von veränderlichen und unveränderlichen Typen in Multithread-Umgebungen ganz genauso.
Unser Ziel in diesem Artikel ist es, unveränderliche Typen zu implementieren.  Eine Eigenschaft von unveränderlichen Typen ist, dass alle ihre Methoden read-only-Funktionalität haben, d.h. sie verändern das this-Objekt nicht, sondern "lesen" es nur.  Fangen wir also damit an, dass wir uns überlegen, wie man eigentlich eine lesende Methoden implementiert.
 

Lesende Zugriffsmethoden

Nehmen wir einmal an, wir wollen eine Zugriffsmethode implementieren, die nur lesenden Zugriff auf private Daten eines Objekts gibt. Betrachten wir dazu das folgende Beispiel:

public final class Widget
{
  private Stamp lastModification = null;

  ... other methods and fields ...

  public Stamp getLastModification();

}

Es handelt sich um eine Klasse, die in einem Feld vom Typ Stamp Informationen über die letzte erfolgte Modifikation des Objekts festhält.  Der Typ Stamp ist hier nicht näher ausgeführt, aber man stelle sich vor, er enthält Informationen wie den Zeitpunkt der Veränderung, den Urheber der Veränderung, etc. Das Feld wird an geeigneter Stelle initialisiert und in jeder verändernden Methode mit neuen Werten belegt. Wie das genau gemacht wird, ist an dieser Stelle ohne Belang.  Uns interessiert vielmehr die Zugriffsmethode getLastModification(). Sie gibt Zugriff auf das private Feld.  Dabei soll sie sicher keinen Schreibzugriff gestatten, sondern nur Lesezugriff. Sonst könnte "von Außen" der Zeitstempel manipuliert werden; er soll aber ausschließlich "von Innen", d.. von den Methoden der Klasse Widget, verändert werden. Wie kann man diese nur lesende Zugriffsmethode implementieren?

Also, so ist es sicher falsch:

public final class Widget
{
  private Stamp lastModification;

  ... other methods and fields ...

  public Stamp getLastModification()
  {
    return lastModification ;   // don't do this !!!
  }
}

Hier kann jeder nach dem Aufruf von getLastModification() auf das private Feld der Klasse  Widget zugreifen und den Eintrag ändern.  Zum Beispiel so:

Widget w = new Widget();
...
Stamp log = w.getLastModification();
log.setDate(new Date());
log.setAuthor("Molly Malicious");

Mit der Veränderung des Felds wäre dann die logische Konsistenz des Widget Objekts zerstört. Das sollte eigentlich nicht passieren. Das Problem rührt daher, dass  Stamp  ein veränderlicher Typ ist.  Hat man erst einmal Referenz auf ein Objekt des Typs Stamp, dann hat man nicht nur Lese- sondern auch Schreibzugriff auf das Objekt. Man kann das Problem lösen, indem man das Feld kopiert, damit dem Aufrufer von getLastModification() eine eigene unabhängige Kopie zur Verfügung steht, die er nach Belieben ändern kann, ohne dass es Auswirkungen auf die Klasse  Widget hat.

public final class Widget
{
  private Stamp lastModification;

  ... other methods and fields ...

  public Stamp getLastModification()
  {
    return (Stamp)lastModification.clone();
  }
}

Das Kopieren von Objekten kann u.U. relativ teuer sein, abhängig von der inneren Struktur und Größe des zu kopierenden Objekts. Generell wird man versuchen, den Overhead des Kopierens zu vermeiden, wann immer es geht, schon allein, weil es das neu erzeugte Objekt den Garbage Collector mit zusätzlicher Arbeit belastet.  Und in diesem Fall geht es. Man kann ohne Kopie auskommen,  nämlich mit Hilfe von  unveränderlichen Typen.
 

Unveränderliche Typen

Referenzvariablen von einem unveränderlichen Typ zeigen entweder auf Objekte, die sich tatsächlich nicht verändern, oder sie lassen veränderliche Objekte zumindest so aussehen, als seien sie unveränderlich. Man unterscheidet zwischen Read-Only-Sichten und "echten" unveränderlichen Typen. Im folgenden sehen wir uns Adaptoren an, die diese beiden Arten von unveränderlichen Typen erzeugen.
 

Read-Only-Adaptoren

Sehen wir uns einen Read-Only-Adaptor am Beispiel unserer Stamp Klasse an, die vermutlich die folgenden Methoden haben wird:

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

  ... other methods and fields ...

  public Date getDate()
  {
    return (Date)date.clone();
  }
  public void setDate(Date d)
  {
    date = (Date)d.clone();
  }

  public String getAuthor()
  {
    return author;
  }
  public void setAuthor(String a)
  {
    author = a;
  }
}

Wenn wir ein Interface definieren, das nur die lesenden Methoden der  Klasse  Stamp enthält, dann haben wir eine Read-Only-Sicht auf Objekte des Typs Stamp:

public interface ImmutableStamp
{
  public Date getDate();
  public String getAuthor();
}

Die Klasse  Stamp  kann nun dieses Interface implementieren:

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

  ... other methods and fields ...

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

Mit dem Read-Only-Adaptor wollten wir erreichen, dass wir in der lesenden Zugriffsmethode getLastModification() möglichst ohne Kopieren auskommen. Nun schaffen wir es nicht, das Kopieren von Objekten gänzlich zu vermeiden, weil ja bereits in den Methoden der Stamp-Klasse Kopien erzeugt werden, aber wir können es doch deutlich reduzieren. Man könnte nämlich nun die Methode  getLastModification() so ändern, dass sie  anstelle einer Referenz auf ein Stamp-Objekt eine Referenz vom Typ ImmutableStamp zurück gibt. Das sähe dann so aus:

public final class Widget
{
  private Stamp lastModification;

  ... other methods and fields ...

  public ImmutableStamp getLastModification()
  {
    return lastModification;
  }
}

Durch die Rückgabe einer Referenz vom Typ ImmutableStamp auf das existierende Stamp-Feld des Widget-Objekts haben wir das Kopieren des Stamp-Objekts vermieden.Gleichzeitig haben wir aber mit Hilfe des Read-Only-Interfaces aber auch erreicht, dass der Aufrufer nur noch lesend auf das  Stamp-Feld  zugreifen kann. Das sieht man im nachfolgenden Beispiel:

Widget w = new Widget();
...

ImutableStamp log = w.getLastModification();
log.setDate(new Date());                   // does not compile
log.setAuthor("Molly Malicious");          // does not compile

Bereits zur Compilezeit bekommt man hier eine Fehlermeldung, weil über das Interface ImmutableStamp nur noch die lesenden Methoden getDate() und getAuthor() sichtbar sind.

Read-Only-Adaptoren haben einen gravierenden Haken. Das Stamp-Objekt sieht, durch die Brille des ImmutableStamp-Interfaces gesehen,  nur so aus, als sei es unveränderlich. In Wahrheit kann das Stamp-Objekt natürlich immer noch geändert werden. Wir haben lediglich eine Art Absichtserklärung erreicht: die getLastModification()-Methode gibt zu erkennen, dass sie keinen schreibenden Zugriff auf das  Stamp-Objekt geben möchte. Und in der Tat kann man auch versehentlich über das  ImmutableStamp-Interface keine Veränderungen vornehmen.  Aber das Interface gibt keine Garantie, dass das  Stamp-Objekt tatsächlich unverändert bleibt.  Es könnte ja an anderer Stelle über eine Stamp-Referenz verändert werden.  Und natürlich kann man die  ImmutableStamp-Referenz mit einem expliziten Cast in eine Stamp-Referenz verwandeln, und dann kann man sogar selber verändernd auf das referenzierte  Stamp-Objekt zugreifen.  Ein Read-Only-Adapter gibt also keine Garantie, dass das referenierte Objekt unverändert bleibt.

Ob eine Read-Only-Sicht auf ein veränderliches Objekt nun gut oder schlecht ist, hängt ganz von den Umständen und der Erwartungshaltung ab.  Es kann durchaus erwünscht sein, dass man selbst keine Veränderungen am Shared Object vornehmen will (und dies durch die Read-Only-Sicht zum Ausdruck bringt), man aber die Veränderungen am Shared Object, die von anderen herbeigeführt werden, sehen möchte. Dann ist eine Read-Only-Sicht auf ein veränderliches Objekt völlig in Ordnung.  Es kann aber auch sein, dass man sich auf die Unveränderlichkeit des Objekts verlassen will.  Das ist zum Beispiel bei Shared Objects in Multithread-Programmen der Fall. Die Synchronisation der Zugriffe auf das von mehreren Threads gemeinsam verwendete Objekt kann nur dann entfallen, wenn das Objekt sich tatsächlich nicht ändern kann. Bei Shared Objects in Multithread-Programmen  wäre es ein fataler Fehler, wegen der Read-Only-Sicht auf die Synchronisation zu verzichten, weil das referenzierte Shared Objekt durch den Read-Only-Adapter keineswegs for Veränderungen geschützt ist.

Im Beipiel unserer  getLastModification()-Methode kann man darüber streiten, ob die Read-Only-Sicht auf das veränderliche Stamp-Objekt gut oder schlecht ist.  In jedem Falle sollte aber  sorgfältig dokumentiert sein, was genau die Methode zurück gibt.

Ganz allgemein muss man sich darüber klar sein, was ein  Read-Only-Interface tatsächlich bedeutet: es ist reine Read-Only-Sicht auf etwas möglicherweise Veränderliches. Die Gefahr liegt darin, dass u.U. nicht jedem auf Anhieb klar ist,  dass etwas, das unveränderlich aussieht, dennoch verändert werden kann. Ein Read-Only-Interface könnte zu Missverständnissen führen. Deshalb ist es nicht ganz unproblematisch. Das gleiche gilt übrigens auch für  Superklassen, die Immutability versprechen, dann aber auf Subklassen verweisen können, die gar nicht unveränderlich sind.
 

Duale Klassen

In folgenden wollen wir eine Lösung vorstellen, die beide Aspekte von Immutability abdeckt: die  Read-Only-Sicht auf etwas möglicherweise Veränderliches und die Referenz auf ein echt inveränderliches Objekt. Das kann man mit sogenannten dualen Klassen erreichen. Bei diesem Ansatz ist die Grundidee, dass man zwei verschiedene Klassen, eine für veränderliche und eine für unveränderliche Objekte, hat. Im Beispiel unserer Stamp-Abstraktion würde es neben der  Stamp -Klasse noch eine zweite Klasse  ImmutableStamp  geben, die wie folgt aussähe:

public final class ImmutableStamp
{
  private Stamp stamp;

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

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

Objekte vom Typ ImmutableStamp sind im Prinzip Kopien der korrespondierenden Stamp-Objekte. Sie haben, genau wie unser Read-Only-Interface zuvor, nur die lesenden Methoden getDate() und getAuthor().   Die Zugriffsmethode getLastModification() unserer  Widget-Klasse würde dann wie folgt aussehen:

public final class Widget
{
  private Stamp lastModification;

  ... other methods and fields ...

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

Mit dieser Lösung hat man ebenfalls erreicht, dass die Methode  getLastModification() nur noch lesenden Zugriff auf den Zeitstempel gibt.  Dieses Mal verweist die zurückgelieferte Referenz auf ein Objekt, das wirklich unveränderlich ist, weil es ein ImmutableStamp-Objekt ist, das überhaupt keine verändernden Methoden hat. Anders als in der zuvor besprochenen Adapter-Lösung mit dem ImmutableStamp-Interface, wo wir eine Referenz auf das Original-Stamp-Objekt zurückgeliefert hatten, welches nach wie .vor veränderlich ist.

Nun kann man sich fragen, was man mit dieser Lösung an Kopieraufwand gespart hat.  Zunächst einmal nichts. In unserer Original-Implementierung der Widget-Klasse hatte die Methode getLastModification() einen Klon erzeut. Jetzt passiert genau dasselbe, allerdings implizit im Konstruktor des unveränderlichen Typs. Von nun an spart man aber Kopieraufwände ein, weil man mit dem ImmutableStamp-Objekt ein unverändliches Objekt hat, das man nie mehr kopieren muss und das man immer per Referenz weiterreichen kann. Wenn die ImmutableStamp-Klasse nicht existiert, dann gibt es keine Möglichkeit sicher zu stellen, dass  in einem bestimmten Kontext Schreibzugriffe ausgeschlossen sind.  Im Zweifelsfall muss man dann Kopien von Stamp-Objekten erzeugen, um sich gegen Veränderungen an gemeinsam verwendeten Stamp-Objekten zu schützen, so wie wir das in unserer allerersten Lösung gemacht hatten.

Klassen, die wie unser Stamp/ImmutableStamp-Paar in zwei Ausprägungen daher kommen, bezeichnet man als duale Klassen.  Das wohl bekannteste Beipiel für ein solches Paar ist die String-Abstraktion im JDK, die in Form der beiden Klassen  String und StringBuffer implementiert ist.
 

Duale Klassen im Detail

Mit der dualen Klasse haben wir eine Möglichkeit gefunden, sowohl veränderliche als auch unveränderliche Ausprägungen einer Abstraktion zu verwenden.  Fehlt uns also noch die Read-Only-Sicht für all die Situationen, in denen wir durch die Read-Only-Brille auf ein veränderliches Objekt blicken wollen.

Die Read-Only-Sicht auf eine duale Klasse drückt man aus durch eine gemeinsame Superklasse oder ein gemeinsames Super-Interface, das es erlaubt, die beiden Typen von Objekten austauschbar zu verwenden.  Das ist nützlich, wenn man Schnittstellen hat, denen es egal ist,  ob die Objekte veränderlich oder unveränderlich sind.  Diese Schnittstellen würden dann so deklariert, dass sie mit Superklassen- oder Super-Interface-Referenzen arbeiten, hinter denen sich beide Ausprägungen der Abstraktion verbergen können. Diese gemeinsame Superklasse oder das gemeinsame Super-Interface ist dann die Read-Only-Sicht auf beiden Arten von Objekten.

Im Beispiel unserer Stamp-Abstraktion sähe das so aus, wenn man ein gemeinsames Interface definiert:

public interface StampBase
{
 public Date getDate();
 public String getAuthor();
}
 

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

  ... other fields and methods ...

  public Stamp(Date d, String a)
  {
    date = (Date)d.clone():
    author = a;
  }
  public Date getDate()
  {
    return (Date)date.clone();
  }
  public void setDate(Date d)
  {
    date = (Date)d.clone();
  }

  public String getAuthor()
  {
    return author;
  }
  public void setAuthor(String a)
  {
    author = a;
  }
}

public final class ImmutableStamp implements StampBase
{
  private Stamp stamp;

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

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

Man kann auch noch einen Schritt weiter gehen und Gemeinsamkeiten der beiden Stamp-Klassen in eine gemeinsame Superklasse herausziehen. Das sieht dann wie folgt aus:

public class StampBase
{
 protected Date date;
 protected String author;

 ... other common fields ...

 public StampBase(Date d, String a)
 {
   date = (Date)d.clone():
   author = a;
 }

 public Date getDate()
  {
    return (Date)date.clone();
  }
  public String getAuthor()
  {
    return author;
  }

  ... other common read-only methods ...
}
 

public final class Stamp extends StampBase
{
  public Stamp(Date d, String a)
  {
    super(d,a);
  }
  public Stamp(StampBase s)
  {
    super(s.date,s.author);
  }

  public void setDate(Date d)
  {
    date = (Date)d.clone();
  }

  public void setAuthor(String a)
  {
    author = a;
  }

  ... other mutating methods ...
}

public final class ImmutableStamp extends StampBase
{
  public ImmutableStamp(Date d, String a)
  {
    super(d,a);
  }
  public ImmutableStamp(StampBase s)
  {
    super(s.date,s.author);
  }
}

In beiden Fällen sollte man aber nicht etwa auf die Idee kommen, Stamp von ImmutableStamp abzuleiten. Diese Idee ist ziemlich naheliegend; schließlich würde man dann die lesenden Methoden erben und müßte sie nicht re-implementieren. Wenn man diese Ableitung macht, dann ist die Semantik der ImmutableStamp-Klasse radikal anders: sie gibt keine Immutability-Garantie mehr.  Die ImmutableStamp-Klasse degeneriert dann zu einer Read-Only-Sicht. Das liegt daran, dass   eine Referenzvariable vom Typ ImmutableStamp wegen der Vererbungsbeziehung auf ein veränderliches Stamp-Objekt verweisen kann. Das ist nicht die Idee einer dualen Klasse.

Bei der dualen Klasse hat man getrennte Typen für die veränderliche und die unveränderliche Ausprägung der Abstraktion. Die beiden Typen sind nicht  zuweisungsverträglich, d.h. nicht voneinander abgeleitet. Variablen des einen Typs können nicht in Variablen des anderen Type gecastet werden. Allerdings sind "Konvertierungen" möglich, indem aus dem Objekt des einen Typs ein Objekt des anderen Type konstruktiert wird.  Das sind aber keine Typkonvertierungen, sondern Objektkonvertierungen, die Kopieraufwände beinhalten.

Typkonvertierungen sind möglich zwischen dem dritten Supertyp (falls vorhanden) und den beiden Sybtypen.  Das heißt, man kann eine Read-Only-Sicht (durch den gemeinsame Supertyp) auf beide Arten von Objekten haben, und man kann zwischen der Read-Only-Sicht und der uneingeschränkten "echten" Sicht hin und her konvertieren. Bei dieser Art der Konvertierung sind werden keine Kopien gemacht, sondern nur Sichten verändert.  Zwischen den beiden Subtypen, dem  veränderlichen und unveränderlichen Typ, kann jedoch nicht konvertiert werden.

Mit dualen Klassen ist man nun sehr flexibel:

  • Man hat die Möglichkeit, Methoden zu implementieren, die mit StampBase-Referenzen arbeiten.  Solche Methoden greifen nur lesend auf die Stamp-Objekte zu, interessieren sich aber desweiteren nicht dafür, ob die Stamp-Objekte veränderlich oder unveränderlich sind. Man kann auch jederzeit einen Cast auf den veränderlichen oder unveränderlichen Subtyp machen, wenn das gebraucht wird.
  • Wo man sicherstellen muss, dass sich Stamp-Objekte nicht ändern, kann man ImmutableStamp-Objekte verwenden.  Über den Konstruktor ImmutableStamp(StampBase s) kann man  jederzeit aus veränderlichen Stamp-Objekten unveränderliche  ImmutableStamp-Objekte erzeugen.
  • Und wo die gesamte Funktionalität der Stamp-Abstraktion inklusive der verändernden Methoden gebraucht wird, da kann man mit Stamp-Objekten arbeiten, die man ebenfalls per Konstruktion aus ImmutableStamp-Objekten erzeugen kann.

Marker-Interface für Immutability

Leider bietet die Sprache Java praktisch keine Unterstützung für die Immutability an.  Es gibt ein winziges bischen an Unterstützung in Form des Schlüsselwortes final; darauf kommen wir in der nächsten Ausgabe der Kolumne zurück. Ansonsten ist man auf Konventionen und Programmierdisziplin angewiesen. Die Tatsache, dass eine Klasse unveränderlich ist, kann man nur im Namen der Klasse und/oder in der Dokumentation zur Klasse ausdrücken.

Wer es etwas deutlicher sagen will, kann sich ein leeres Marker-Interface Immutable definieren und alle unveränderlichen Klassen davon ableiten.  Das hat den Vorteil, dass man zur Laufzeit ein Objekt mit Hilfe des instanceof-Operators fragen kann, ob es unveränderlich ist.

Aber auch das bietet keine absolute Sicherheit, insbesondere dann nicht, wenn Vererbung im Spiel ist. Wir haben in unserem Beispiel einer dualen Abstraktion bewußt die veränderliche und die unveränderliche Klasse als final Klassen deklariert.  Bei einer non-final Klasse, die unveränderlich ist, ist es reine Disziplin und guter Wille, dass diese Semantik in den Subklassen auch beibehalten wird.
 

Zusammenfassung und Ausblick

In diesem Artikel haben wir uns angesehen, wie man unveränderliche Typen implementiert. Unveränderliche Typen sind nützlich, weil sie die Notwendigkeit, Kopien von Objekten zu erzeugen,  reduzieren  und weil sie den Synchronisationsaufwand  in Multithread-Umgebungen vermindern.
Unveränderliche Typen haben nur lesende Methoden und sind idealerweise final Klassen oder haben nur Subklassen, die ebenfalls unveränderliche Typen sind. Unveränderliche Typen sollte man nicht mit Read-Only-Sichten auf veränderliche Typen verwechseln.
Als duale Klasse bezeichnet man Abstraktionen, die als Paar von einer veränderlichen und einer unveränderlichen Klasse implementiert sind. Duale Klassen haben häufig einen gemeinsamen Supertyp, der die Read-Only-Sicht auf beide Klassen repräsentiert.
In der nächsten Ausgabe dieser Kolumne sehen wir uns das Schlüsselwort final an und was es mit Immutability zu tun hat. Darüber hinaus untersuchen wir die Immuability-Adaptoren des JDK-Collection-Frameworks.
 
 

Literaturverweise

 
/KRE1/ Das Kopieren von Objekten in Java (Teil 1 - 3) 
Klaus Kreft & Angelika Langer
JavaSpektrum, September 2002 + November 2002 + Januar 2003
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/05.Clone-Part1/05.Clone-Part1.html
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/06.Clone-Part2/06.Clone-Part2.html
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/07.Clone-Part3/07.Clone-Part3.html
/KRE2/ Unveränderliche Typen in Java (Teil 2) 
Klaus Kreft & Angelika Langer
JavaSpektrum, Juli 2003
http://www.AngelikaLanger.com/Articles/EffectiveJava/09.Immutability-Part2/09.Immutability-Part2.html
/DAV1/  Durable Java – Immutables
Mark Davis
URL:  http://www.macchiato.com/columns/Durable2.htm
/DAV2/  Durable Java – Abstraction
Mark Davis
URL:  http://www.macchiato.com/columns/Durable3.htm
/GOE1/  Java theory and practice: To mutate or not to mutate?
Immutable objects can greatly simplify your life
Brian Goetz
IBM developerWorks, February 18, 2003
URL: http://www-106.ibm.com/developerworks/java/library/j-jtp02183.html
/GOE2/  Java theory and practice: Is that your final answer?
Guidelines for the effective use of the final keyword
Brian Goetz
IBM developerWorks, October 1, 2002
URL: http://www-106.ibm.com/developerworks/java/library/j-jtp1029.html

 
 

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/08.Immutability-Part1/08.Immutability-Part1.html  last update: 26 Nov 2008