Parametrischer Code


... [ Seminar "Java und Werkzeuge für das Web" ] ... [ Inhaltsverzeichnis ] ... [ zurück ] ... [ weiter ] ...

Übersicht: Parametrischer Code


Parametrische Klassen

Es folgt nun ein Beispiel mit einer Collection, wie man es in Java 1.4.1 schreibt:

01 interface Collection {
02   public void add(Object x);
03   public Iterator iterator();
04 }
05 
06 interface Iterator {
07   public Object next();
08   public boolean hasNext();
09 }
10 
11 class NoSuchElementException extends RuntimeException {}
12 
13 class LinkedList implements Collection {
14   protected class Node {
15     Object elt;
16     Node next = null;
17     Node(Object elt){ this.elt = elt; }
18   }
19 
20   protected Node head = null, tail = null;
21   public LinkedList(){}
22   public void add(Object elt){
23     if(head==null){ head = new Node(elt); tail = head; }
24     else { tail.next = new Node(elt); tail = tail.next; }
25   }
26 
27   public Iterator iterator(){
28     return new Iterator(){
29       protected Node prt = head;
30       public boolean hasNext(){ return ptr!=null; }
31       public Object next(){
32         if(ptr!=null){
33           Object elt=ptr.elt; ptr=ptr.next; return elt;
34         } else
35           throw new NoSuchElementException();
36       }
37     };
38   }
39 }
Bsp/Bsp03.txt

Codebeispiel 3


Diese Basisklassen werden in einem Testprogramm genutzt:

01 class Test {
02   public static void main(String[] args){
03     LinkedList xs = new LinkedList();
04     xs.add(new Byte(0)); xs.add(new Byte(0));
05     Byte x = (Byte) xs.iterator().next();
06 
07     LinkedList ys = new LinkedList();
08     ys.add("zero"); ys.add("one");
09     String y = (String) ys.iterator().next();
10 
11     LinkedList zss = new LinkedList();
12     zss.add(ys);
13     String z = (String)((LinkedList)zss.iterator().next()).iterator().next();
14 
15     Byte w = (Byte) ys.iterator().next(); // runtime-exception
16   }
17 }
Bsp/Bsp04.txt

Codebeispiel 4


Hier ist zu beachten, dass jede Entnahme aus dem Container einen Down-Cast erfordert. Dieser kann, wie in der letzten Zeile, zur Laufzeit fehlschlagen. Verschachtelt man Container, so kann durch die Klammern und Casts schwer lesbarer Code entstehen. Dies ist in der vorletzten Zeile geschehen.

Wie sieht nun das Codebeispiel 03 mit Generizität aus?

01 interface Collection<A> {
02   public void add(A x);
03   public Iterator iterator();
04 }
05 
06 interface Iterator<A> {
07   public A next();
08   public boolean hasNext();
09 }
10 
11 class NoSuchElementException extends RuntimeException {}
12 
13 class LinkedList<A> implements Collection<A> {
14   protected class Node {
15     A elt;
16     Node next = null;
17     Node(A elt){ this.elt = elt; }
18   }
19 
20   protected Node head = null, tail = null;
21   public LinkedList(){}
22   public void add(A elt){
23     if(head==null){ head = new Node(elt); tail = head; }
24     else { tail.next = new Node(elt); tail = tail.next; }
25   }
26 
27   public Iterator<A> iterator(){
28     return new Iterator<A>(){
29       protected Node prt = head;
30       public boolean hasNext(){ return ptr!=null; }
31       public A next(){
32         if(ptr!=null){
33           A elt=ptr.elt; ptr=ptr.next; return elt;
34         } else
35           throw new NoSuchElementException();
36       }
37     };
38   }
39 }
Bsp/Bsp05.txt

Codebeispiel 5


Es ist einfach zu sehen, dass die Klassen um einen zusätzlichen Typparameter erweitert wurden. Dieser wird in spitzen Klammern "<>" angegeben. In diesen und den folgenden Beispielen wird meistens ein großes "A" verwendet, es kann aber jeder beliebige Bezeichner verwendet werden und auch mehrere Parameter sind durch Komma getrennt möglich. Der Typparameter ist in der gesamten Klasse sichtbar, mit Ausnahme von statischen Bestandteilen (z.B. können statische Methoden nicht auf den Typparamter zugreifen). Der Typparameter ist eine Eigenschaft eines Objektes und keine statische Information der Klasse. Die Syntax erinnert an die C++ Templates hat aber nicht viel damit gemeinsam. Alle "Object" wurden durch den Typparameter ersetzt. Das Testprogramm sieht entsprechen umgeschrieben so aus:

01 class Test {
02   public static void main(String[] args){
03     LinkedList<Byte> xs = new LinkedList<Byte>();
04     xs.add(new Byte(0)); xs.add(new Byte(0));
05     Byte x = xs.iterator().next();
06 
07     LinkedList<String> ys = new LinkedList<String>();
08     ys.add("zero"); ys.add("one");
09     String y = ys.iterator().next();
10 
11     LinkedList<LinkedList<String>> zss = new LinkedList<LinkedList<String>>();
12     zss.add(ys);
13     String z = zss.iterator().next().iterator().next();
14 
15     Byte w = ys.iterator().next(); // compile-error!
16   }
17 }
Bsp/Bsp06.txt

Codebeispiel 6


Mehrere Dinge werden hier deutlich:

Gerade der dritte Punkt ist besonders wichtig. Ein Fehler, der bisher erst zur Laufzeit aufgetreten ist, kann jetzt bereits beim Compilieren vom Compiler zurückgewiesen werden. Das ist nicht zu unterschätzen. Je früher ein Fehler in einem Programm gefunden wird, umso leichter, schneller und damit auch kostengünstiger ist es ihn zu beheben. Das Beste was einem Programmierer passieren kann, ist also eine Fehlermeldung vom Compiler! Cast sollten grundsätzlich vermieden werden, da sie die Typüberprüfung des Compiler teilweise außer Kraft setzen und die Prüfung auf die zu späte Laufzeit verschieben.

Ein interessanter Punkt, der aber für den Programmierer keine Bedeutung hat, ist auch das Problem der doppelten spitzen Klammern ">>". Diese haben in Java bereits die Bedeutung eines rechtsshift von Integerwerten, was in diesem Kontext natürlich keinen Sinn macht. Die Javagrammatik wurde entsprechend umgestellt, so dass ">>" bei geschachtelten Typparametern ohne ein Leerzeichen geschrieben werden können. Das ist auch ein Unterschied zu C++, wo die schließenden Klammern durch ein Leerzeichen getrennt werden müssen (auch wegen dem vergebenen ">>" Operator). Die Syntax ist damit einfacher zu benutzen als in C++.


[ nach oben ]

Parametrische Methoden

Was wir bisher gesehen haben, waren parametrisierte Klassen. Nun gibt es aber noch eine andere Anwendung von Generizität: parametrische Methoden. Hier wird eine Methode mit einem Typ parametrisiert. Dies ist unabhängig von parametrisierten Klassen nutzbar, kann aber auch kombiniert werden. Dazu zunächst wieder ein Beispiel ohne Generizität:

01 interface Comparator {
02   public int compare(Object x, Object y);
03 }
04 
05 class ByteComparator implements Comparator {
06   public int compare(Object x, Object y){
07     return ((Byte)x).byteValue() - ((Byte)y).byteValue();
08   }
09 }
10 
11 class Collections {
12   public static Object max(Collection xs, Comparator c){
13     Iterator xi = xs.iterator();
14     Object w = xi.next();
15     while(xi.hasNext()){
16       Object x = xi.next();
17       if(c.compare(w,x) < 0) w = x;
18     }
19     return w;
20   }
21 }
Bsp/Bsp07.txt

Codebeispiel 7


Die zugehörige Testklasse:

01 class Test {
02   public static void main(String[] args){
03     LinkedList xs = new LinkedList();
04     xs.add(new Byte(0)); xs.add(new Byte(0));
05     Byte x = (Byte) Collections.max(xs, new ByteComparator());
06 
07     LinkedList ys = new LinkedList();
08     ys.add("zero"); ys.add("one");
09     // folgende Zeile fuehrt zu einem Laufzeitfehler:
10     String y = (String) Collections.max(ys, new ByteComparator());
11   }
12 }
Bsp/Bsp08.txt

Codebeispiel 8


Die Methode max soll jetzt mit Typen parametrisiert werden. Dies soll unabhängig von Typparametern der Klasse Collections sein. Konkret möchte man ausdrücken, dass die Collection Objekte von einem Typen enthält, der auch von dem Comparator erwartet wird.

Mit Generizität sieht das ganze so aus:

01 interface Comparator<A> {
02   public int compare(A x, A y);
03 }
04 
05 class ByteComparator implements Comparator<Byte> {
06   public int compare(Byte x, Byte y){
07     return x.byteValue() - y.byteValue();
08   }
09 }
10 
11 class Collections {
12   public static <A> A max(Collection<A> xs, Comparator<A> c){
13     Iterator<A> xi = xs.iterator();
14     A w = xi.next();
15     while(xi.hasNext()){
16       A x = xi.next();
17       if(c.compare(w,x) < 0) w = x;
18     }
19     return w;
20   }
21 }
Bsp/Bsp09.txt

Codebeispiel 9


Die statische Methode max wird mit einem Typparameter versehen, der vor ihrer Signatur steht. Der Parameter wird als Rückgabetyp, als Argument und im Methodenrumpf benutzt. Die Testklasse sieht so aus:

01 class Test {
02   public static void main(String[] args){
03     LinkedList<Byte> xs = new LinkedList<Byte>();
04     xs.add(new Byte(0)); xs.add(new Byte(0));
05     Byte x = Collections.max(xs, new ByteComparator());
06 
07     LinkedList<String> ys = new LinkedList<String>();
08     ys.add("zero"); ys.add("one");
09     // naechste Zeile fuehrt jetzt zu einem compile-error:
10     String y = Collections.max(ys, new ByteComparator());
11   }
12 }
Bsp/Bsp10.txt

Codebeispiel 10


Die Umsetzung ist wie erwartet. Eine Tatsache wirft aber Fragen auf: Wie wird in der max-Methode die Typinformation für den Parameter <A> bestimmt? Er wird nicht explizit beim Aufruf der Methode mit angegeben! Hier beginnt ein etwas komplizierteres Kapitel der Generizität in Java, die Typ-Inferenz. Dies wird im nächsten Kapitel besprochen.


[ nach oben ]

weitere Regeln für Typparameter

Es ist illegal, wenn eine Klasse direkt oder indirekt ein parametrisiertes Interface oder eine parametrisierte Klasse mehrfach und mit verschiedenen Parametern implementiert:

01 class B implements I<Integer>
02 class C extends B implements I<String> // compiletime-error
Bsp/Bsp34.txt

Codebeispiel 11


Es ist noch zu beachten, dass parametrisierte Typen nicht direkt oder indirekt von java.lang.Throwable ableiten dürfen, da der Exception-Mechanismus der Virtuellen Maschine keine Generizität unterstützt.


... [ Seminar "Java und Werkzeuge für das Web" ] ... [ Inhaltsverzeichnis ] ... [ zurück ] ... [ weiter ] ... [ nach oben ] ...

valid html4 logo Code generated with AusarbeitungGenerator Version 1.1, weblink