Arrays und Vererbungsbeziehungen
Übersicht: Arrays und Vererbungsbeziehungen
Generische Arrays
Wie bereits angesprochen, wird die Typinformation vom Compiler gelöscht und steht zur Laufzeit nicht mehr zur Verfügung. Daher ist es auch nicht möglich, von einem als Parameter übergebenen Typen ein Objekt zu erzeugen. Lautet der Parameter z.B. A, so ist ein Code mit new A(); nicht erlaubt, da daraus nach der Übersetzung "new Object()" werden würde. Für solche Zwecke muss man eine Fabrik als Parameter übergeben.
Es ist jedoch möglich ein Array von dem Typparameter zu erzeugen: new A[42] ist erlaubt, und führt beim compilieren nur zu einer "unchecked warnung". Dies muss auch so sein, damit z.B. die Collection Klassen intern mit Arrays von parametrisierten Typen arbeiten können. In der GJ Spezifikation war eine Fabrikmethode zum Erzeugen von Arrays mit parametrisierten Typen vorgesehen, die in der Public Draft Specification für Java 1.5. jedoch nicht erwähnt wird. Es ist daher empfehlenswert ein Array dieser Art immer außerhalb der parametrisierten Klasse zu erzeugen (wo der Typ bekannt ist) und dann als Parameter in die Klasse zu stecken.
Ein kleines Beispiel das zeigt, welche Probleme auftreten können:
01 class BadArray {
02 public static <A> A[] singleton (A x) {
03 return new A[]{ x }; // unchecked warning
04 }
05 public static void main (String[] args) {
06 String[] a = singleton("zero"); // run-time exception
07 }
08 }
|
Bsp/Bsp23.txt |
Codebeispiel 42
Der Code wird mit einer Warnmeldung übersetzt. Zur Laufzeit tritt dann auch eine Exception auf. Der übersetzte Code sieht so aus:
01 class BadArray {
02 public static Object[] singleton (Object x) {
03 return new Object[]{ x };
04 }
05 public static void main (String[] args) {
06 String[] a = (String[])singleton("zero"); // run-time exception
07 }
08 }
|
Bsp/Bsp24.txt |
Codebeispiel 43
Das Problem ist der Cast von Object[] in String[]. Dies ist nicht erlaubt. In NextGen könnte dieser Fehler nicht passieren, da der Compiler dort den Code nicht in "Object[]" umsetzen würde ([
Cartwright98], S.2). Ganz allgemein bekommt man Probleme, wenn das Array den Gültigkeitsbereich des Typparameters verlässt.
Ableiten von parametrischen Typen
Die Ableitung von parametrisierten Typen ist invariant. Das bedeutet, dass z.B. LinkedList<String> kein Subtyp von LinkedList<Object> ist, obwohl String ja von Object abgeleitet ist. Der Grund dafür ist ganz einfach. Wäre diese Ableitung covariant, könnte man das Typsystem umgehen. Ein Beispiel:
01 class Loophole {
02 public static String loophole (Byte y) {
03 LinkedList<String> xs = new LinkedList<String>();
04 LinkedList<Object> ys = xs; // compile-time error
05 ys.add(y);
06 return xs.iterator().next();
07 }
08 }
|
Bsp/Bsp20.txt |
Codebeispiel 44
Dieser Code muss vom Compiler zurückgewiesen werden, da sonst in der vorletzten Zeile ein Byte in eine String-Liste eingefügt werden könnte. Dieses Verhalten, dass parametrisierte Typen invariant sind, ist übrigens unterschiedlich zu den Arrays in Java. Diese sind covariant: String[] ist ein Subtyp von Object[]. Der entsprechende Code aus Beispiel 20 würde mit Arrays kompiliert werden, aber in der Zuweisung eine Exception zur Laufzeit auslösen. Arrays behalten ihre Typinformation zur Laufzeit und können daher derartige Typverstöße erkennen.
Die Ableitung einer parametrisierten Klasse verhält sich übrigens wie erwartet, wenn man den parametrisierten Typ nicht ändert: LinkedList<String> kann ohne weiteres an Collection<String> zugewiesen werden.
Cast von parametrischen Typen
Da zur Laufzeit keine Informationen über den Typparameter zur Verfügung stehen, werden einige Casts vom Compiler als Fehler abgelehnt. Es gibt auch teilweise keine Möglichkeit einen instanceof Test zu machen. Nur wenn der Cast durch eine Kombination von Typinformationen zur Compile- und Laufzeit geprüft werden kann, ist er zulässig. Am besten zeigt dies ein Beispielcode:
01 class Convert {
02 public static <A> Collection<A> up (LinkedList<A> xs) {
03 return (Collection<A>)xs;
04 }
05 public static <A> LinkedList<A> down (Collection<A> xs) {
06 if (xs instanceof LinkedList<A>) return (LinkedList<A>)xs;
07 else throw new ConvertException();
08 }
09 }
|
Bsp/Bsp25.txt |
Codebeispiel 45
Dieser Code enthält zwei legale Casts und einen instanceof Test der so auch funktioniert. Zur Laufzeit wird in der down-Methode geprüft, ob xs vom Rawtype "LinkedList" ist. Der Compiler wird darüber hinaus sicherstellen, dass der Typparameter stimmt. Es kann nur eine Collection mit A als Inhalt übergeben werden, so dass der Cast in LinkedList korrekt funktioniert und zum Rückgabetyp passt. Was nicht geht:
01 class BadConvert {
02 public static Object up (LinkedList<String> xs) {
03 return (LinkedList<Object>) xs; // compile-time error
04 }
05 public static LinkedList<String> down (Object o) {
06 if (o instanceof LinkedList<String>) // compile-time error
07 return (LinkedList<String>)o; // compile-time error
08 else throw new ConvertException();
09 }
10 }
|
Bsp/Bsp26.txt |
Codebeispiel 46
Hier gibt es an drei Stellen einen Compilerfehler. Der instanceof-Test und die Casts schlagen fehl. In der GJ-Spezifikation wird dafür eine Lösung durch Wrapper-Klassen vorgeschlagen, die aber mehr eine Krücke als eine Lösung ist. Auch die Definition einer speziellen Klasse, die von der parametrisierten ableitet und den Parameter festlegt, ist nur eine Notlösung. Zwar können in beiden Fällen zur Laufzeit instanceof und casts verwendet werden, aber ist ja nicht der Sinn von Generizität, dass man für jede Ausprägung eine eigene Wrapperklasse schreiben muss. Nur Typinformation zur Laufzeit, wie in NextGen, kann hier einfache Verhältnisse schaffen und würde auch besser in das bisherige Typsystem von Java passen, da dort Typinformation zur Laufzeit (z.B. auch bei Arrays) erhalten bleibt. NextGen macht im Hintergrund auch nichts weiter als Wrapper-Klassen zu erzeugen. Dies wird in den Schlussbemerkungen noch mal angesprochen.
Wann ein Typ S in einen Typ T gecastet werden kann, ist durch folgende Regeln festgelegt:
- T ist ein Subtyp von S und es gibt keine anderen Subtypen von S mit der gleichen Typauslöschung von T
- T ist ein Supertyp von S
Es ist darüber hinaus immer möglich in den Rawtype zu casten.
01 class Dictionary<A,B> extends Object {...}
02 class Hashtable<A,B> extends Dictionary<A,B> {...}
03
04 Dictionary<String,Integer> d;
05 Object o;
06
07 (Hashtable<String,Integer>) d; // legal
08 (Hashtable) o; // legal
09 (Hashtable<Float,Double>) d; // illegal, kein Subtyp
10 (Hashtable<String,Integer>) o; // illegal, kein eindeutiger Subtyp
|
Bsp/Bsp36.txt |
Codebeispiel 47
Code generated with AusarbeitungGenerator Version 1.1, weblink