Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Exceptions
7 Generics<T>
8 Äußere.innere Klassen
9 Besondere Klassen der Java SE
10 Architektur, Design und angewandte Objektorientierung
11 Die Klassenbibliothek
12 Bits und Bytes und Mathematisches
13 Datenstrukturen und Algorithmen
14 Threads und nebenläufige Programmierung
15 Raum und Zeit
16 Dateien, Verzeichnisse und Dateizugriffe
17 Datenströme
18 Die eXtensible Markup Language (XML)
19 Grafische Oberflächen mit Swing
20 Grafikprogrammierung
21 Netzwerkprogrammierung
22 Verteilte Programmierung mit RMI
23 JavaServer Pages und Servlets
24 Datenbankmanagement mit JDBC
25 Reflection und Annotationen
26 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Stichwort
Ihre Meinung?

Spacer
 <<   zurück
Java ist auch eine Insel von Christian Ullenboom
Das umfassende Handbuch
Buch: Java ist auch eine Insel

Java ist auch eine Insel
geb., mit DVD
1482 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1506-0
Pfeil 7 Generics<T>
  Pfeil 7.1 Einführung in Java Generics
    Pfeil 7.1.1 Mensch versus Maschine: Typprüfung des Compilers und der Laufzeitumgebung
    Pfeil 7.1.2 Taschen
    Pfeil 7.1.3 Generische Typen deklarieren
    Pfeil 7.1.4 Generics nutzen
    Pfeil 7.1.5 Generische Schnittstellen
    Pfeil 7.1.6 Generische Methoden/Konstruktoren und Typ-Inferenz

»Zu mancher richtigen Entscheidung kam es nur, weil der Weg zur falschen gerade nicht frei war.« – Hans Krailsheimer (1888–1958)

7 Generics<T>


Rheinwerk Computing - Zum Seitenanfang

7.1 Einführung in Java Generics  Zur nächsten ÜberschriftZur vorigen Überschrift

Generics zählen zu den komplexesten Sprachkonstrukten in Java. Wir wollen uns Generics in zwei Schritten nähern: von der Seite des Nutzers und von der Seite des API-Designers. Das Nutzen von generisch deklarierten Typen ist deutlich einfacher, sodass wir diese niedrig hängende Frucht zuerst pflücken wollen. Das Java-Buch für Fortgeschrittene dokumentiert sehr detailliert Generics aus der Sicht des API-Designers; die gepflückten Früchte werden dann veredelt.


Rheinwerk Computing - Zum Seitenanfang

7.1.1 Mensch versus Maschine: Typprüfung des Compilers und der Laufzeitumgebung  Zur nächsten ÜberschriftZur vorigen Überschrift

Eine wichtige Eigenschaft von Java ist, dass der Compiler die Typen prüft und so weiß, welche Eigenschaften vorhanden sind und welche nicht. Hier unterscheidet sich Java von dynamischen Programmiersprachen wie Python oder PHP, die erst spät eine Prüfung zur Laufzeit vornehmen.

In Java gibt es zwei Instanzen, die die Typen prüfen, und diese sind unterschiedlich schlau. Wir haben die JVM mit der absoluten Typ-Intelligenz, die unsere Anwendung ausführt und als letzte Instanz prüft, ob wir ein Objekt nicht einem falschen Typ zuweisen. Dann haben wir noch den Compiler, der zwar gut prüft, aber teilweise etwas zu gutgläubig ist, und dem Entwickler folgt. Macht der Entwickler Fehler, kann dieser die JVM ins Verderben stürzen und zu einer Exception führen. Alles hat mit der expliziten Typanpassung zu tun.

Ein zunächst unkompliziertes Beispiel:

Object o = "String";
String s = (String) o;

Dem Compiler wird über den expliziten Typecast das Object o für ein String verkauft. Das ist in Ordnung, weil ja o tatsächlich ein String-Objekt referenziert. Problematisch wird es, wenn der Typ nicht auf String gebracht werden kann, wir dem Compiler aber eine Typanpassung anweisen:

Object o = Interger.valueOf( 42 );       // oder mit Autoboxing: Object o = 42;
String s = (String) o;

Der Compiler akzeptiert die Typanpassung, und es folgt kein Fehler zur Übersetzungszeit. Es ist jedoch klar, dass diese Anpassung von der JVM nicht durchgeführt werden kann – daher folgt zur Laufzeit eine ClassCastException, da eben ein Integer nicht auf String gebracht werden kann.

Bei Generics geht es nun darum, dem Compiler mehr Informationen über die Typen zu geben und ClassCastException-Fehler zu vermeiden.


Rheinwerk Computing - Zum Seitenanfang

7.1.2 Taschen  Zur nächsten ÜberschriftZur vorigen Überschrift

In unseren vorangehenden Beispielen drehte sich alles um Spieler und in einem Raum platzierte Spielobjekte. Stellen wir uns vor, der Spieler hat eine Tasche (engl. »pocket«), die etwas enthält. Da nicht bekannt ist, was genau er in der Tasche hat, müssen wir einen Basistyp nehmen, der alle möglichen Objekttypen repräsentiert. Das soll in unserem ersten Beispiel der allgemeinste Basistyp Object sein, sodass der Benutzer alles in seiner Tasche tragen kann: [Primitive Datentypen können über Wrapper-Objekte gespeichert werden, was seit Java 5 dank Auto-boxing leicht möglich ist. Der nachfolgende Abschnitt 9.2, »Wrapper-Klassen und Autoboxing«, erklärt diese Eigenschaft genauer. ]

Listing 7.1  com/tutego/insel/nongeneric/Pocket.java, Pocket

public class Pocket
{
  private Object value;

  public Pocket() {}

  public Pocket( Object value ) { this.value = value; }

  public void set( Object value ) { this.value = value; }

  public Object get() { return value; }

  public boolean isEmpty() { return value == null; }

  public void empty() { value = null; }
}

Es gibt einen Standard- sowie einen parametrisierten Konstruktor. Mit set() lassen sich Objekte in die Tasche setzen und über die Zugriffsmethode get() wieder auslesen.

Geben wir einem Spieler eine rechte und eine linke Tasche:

Listing 7.2  com/tutego/insel/nongeneric/Player.java, Player

public class Player
{
  public String name;
  public Pocket rightPocket;
  public Pocket leftPocket;
}

Zusammen mit einem Spieler, der eine rechte und eine linke Tasche hat, ist ein Beispiel schnell geschrieben. Unser Spieler michael soll sich in beide Taschen Zahlen legen. [Das ist unproblematischer als Diprivan und Demerol… ] Dann wollen wir sehen, in welcher Tasche er die größere Zahl versteckt hat.

Listing 7.3  com/tutego/insel/nongeneric/PlayerPocketDemo.java, main()

Player michael = new Player();
michael.name = "Omar Arnold";
Pocket pocket = new Pocket();
Long aBigNumber = 11111111111111L;
pocket.set( aBigNumber );                      // (1)
michael.leftPocket  = pocket;
michael.rightPocket = new Pocket( 2222222222222222222L );

System.out.println( michael.name + " hat in den Taschen " +
                    michael.leftPocket.get() + " und " + michael.rightPocket.get() );

Long val1 = (Long) michael.leftPocket.get();   // (2)
Long val2 = (Long) michael.rightPocket.get();

System.out.println( val1.compareTo( val2 ) > 0 ? "Links" : "Rechts" );

Das Beispiel hat keine besonderen Fallen, allerdings fallen zwei Sachen auf, die prinzipiell unschön sind. Das hat damit zu tun, dass die Klasse Pocket mit dem Typ Object zum Speichern der Tascheninhalte sehr allgemein deklariert wurde und alles aufnehmen kann:

  • Beim Initialisieren wäre es gut, zu sagen, dass die Tasche nur einen bestimmen Typ (etwa Long) aufnehmen kann. Wäre eine solche Einschränkung möglich, dann lassen sich wie in Zeile (1) auch wirklich nur Long-Objekte in die Tasche setzen und nichts anderes, etwa Integer-Objekte.
  • Beim Entnehmen (2) des Tascheninhalts mit get() müssen wir uns daran erinnern, was wir hineingelegt haben. Fordern Datenstrukturen besondere Typen, dann sollte dieses auch dokumentiert sein. Doch wenn der Compiler wüsste, dass in der Tasche auf jeden Fall ein Long ist, dann könnte die Typanpassung wegfallen und der Programmcode wäre kürzer. Auch könnte uns der Compiler warnen, wenn wir versuchen würden, das Long als Integer aus der Tasche zu ziehen. Unser Wissen möchten wir gerne dem Compiler geben! Denn wenn in der Tasche ein Long-Objekt ist, wir es aber als Integer annehmen und eine explizite Typanpassung auf Integer setzen, meldet der Compiler zwar keinen Fehler, aber zur Laufzeit gibt es eine böse ClassCastException.

Um es auf den Punkt zu bringen: Der Compiler berücksichtigt im oberen Beispiel die Typsicherheit nicht ausreichend. Explizite Typanpassungen sind in der Regel unschön und sollten vermieden werden. Aber wie können wir die Taschen typsicher machen? Eine Lösung wäre, eine neue Klasse für jeden in der Tasche zu speichernden Typ zu deklarieren, also einmal eine PocketLong, dann vielleicht PocketInteger, PocketString, PocketGameObject usw. Nun dürfte klar sein, dass dies keine vernünftige Lösung ist; wir können nicht für jeden Datentyp eine neue Klasse schreiben, und die Logik bleibt die gleiche. Wir wollen wenig schreiben, aber Typsicherheit beim Compilieren haben und nicht erst die Typsicherheit zur Laufzeit, wo uns vielleicht eine ClassCastException überrascht. Es wäre gut, wenn wir den Typ bei der Deklaration frei, allgemein, also »generisch« halten können, und sobald wir die Tasche benutzen, den Compiler dazu bringen könnten, auf diesen dann angegebenen Typ zu achten und die Korrektheit der Nutzung sicherzustellen.

Die Lösung für dieses Problem heißt Generics. [In C++ werden diese Typen von Klassen parametrisierte Klassen oder Templates (Schablonen) genannt. ] Diese Technik wurde in Java 5 eingeführt. Sie bietet Entwicklern ganz neue Möglichkeiten, um Datenstrukturen und Algorithmen zu programmieren, die von einem Datentyp unabhängig, also generisch sind.


Rheinwerk Computing - Zum Seitenanfang

7.1.3 Generische Typen deklarieren  Zur nächsten ÜberschriftZur vorigen Überschrift

Wollen wir Pocket in einen generischen Typ umbauen, so müssen wir an den Stellen, an denen Object vorkam, einen Typstellvertreter, einen so genannten formalen Typparameter einsetzen, der durch eine Typvariable repräsentiert wird. Der Name der Typvariablen muss in der Klassendeklaration angegeben werden.

Die Syntax für den generischen Typ von Pocket ist folgende:

Listing 7.4  com/tutego/insel/generic/Pocket.java, Pocket

public class Pocket<T>
{
  private T value;

  public Pocket() {}

  public Pocket( T value ) { this.value = value; }

  public void set( T value ) { this.value = value; }

  public T get() { return value; }

  public boolean isEmpty() { return value != null; }

  public void empty() { value = null; }
}

Wir haben die Typvariable T definiert und verwenden diese jetzt anstelle von Object in der Pocket-Klasse.

Bei generischen Typen steht die Angabe der Typvariable nur einmal zu Beginn der Klassendeklaration in spitzen Klammern hinter dem Klassennamen. Der Typparameter kann nun fast [T t = new T(); ist zum Beispiel nicht möglich. ] überall dort genutzt werden, wo auch ein herkömmlicher Typ stand. In unserem Beispiel ersetzen wir direkt Object durch T, und fertig ist die generische Klasse.


Namenskonvention Formale Typparameter sind in der Regel einzelne Großbuchstaben wie T (steht für Typ), E (Element), K (Key/Schlüssel), V (Value/Wert). Sie sind nur Platzhalter und keine wirklichen Typen. Möglich wäre etwa auch Folgendes, doch davon ist absolut abzuraten, da Dwarf viel zu sehr nach einem echten Klassentyp als nach einem formalen Typparameter aussieht:

public class Pocket<Dwarf>
{
  private Dwarf value;
  public void set( Dwarf value ) { this.value = value;
  public Dwarf get() { return value;
}

Wofür Generics noch gut ist

Es gibt eine ganze Reihe von Beispielen, in denen Speicherstrukturen wie unsere Tasche nicht nur für einen Datentyp Long sinnvoll sind, sondern grundsätzlich für alle Typen, wobei aber die Implementierung (relativ) unabhängig vom Typ der Elemente ist. Das gilt zum Beispiel für einen Sortieralgorithmus, der mit der Ordnung der Elemente arbeitet. Wenn zwei Elemente größer oder kleiner sein können, muss ein Algorithmus lediglich diese Eigenschaft nutzen können. Es ist dabei egal, ob es Zahlen vom Typ Long, Double oder auch Strings oder Kunden sind – der Algorithmus selbst ist davon nicht betroffen. Der häufigste Einsatz von Generics sind Container, die typsicher gestaltet werden sollen.


Hinweis Die Idee, Generics in Java einzuführen, ist schon älter und geht auf das Projekt Pizza beziehungsweise das Teilprojekt GJ (A Generic Java Language Extension) von Martin Odersky (auch Schöpfer der Programmiersprache Scala), Gilad Bracha, David Stoutamire und Philip Wadler zurück. GJ wurde dann die Basis des JSR 14: Add Generic Types To The Java Programming Language.



Rheinwerk Computing - Zum Seitenanfang

7.1.4 Generics nutzen  Zur nächsten ÜberschriftZur vorigen Überschrift

Um die neue Pocket-Klasse nutzen zu können, müssen wir sie zusammen mit einem Typparameter angeben; es entsteht der parametrisierte Typ:

Listing 7.5  com/tutego/insel/generic/PocketPlayer.java, main() Teil 1

Pocket<Integer>  intPocket     = new Pocket<Integer>();
Pocket<String>   stringPocket  = new Pocket<String>();

Der konkrete Typ steht immer hinter dem Klassen-/Schnittstellennamen in spitzen Klammern. [Dass auch XML in spitzen Klammern daherkommt und XML als groß und aufgebläht gilt, wollen wir nicht als Parallele zu Javas Generics sehen. ] Die Tasche intPocket ist eine Instanziierung eines generischen Typs mit dem konkreten Typargument Integer. Diese Tasche kann jetzt offiziell nur Integer-Werte enthalten, und die Tasche stringPocket enthält nur Zeichenketten. Das prüft der Compiler auch, und wir benötigen keine Typanpassung mehr:

Listing 7.6  com/tutego/insel/generic/PocketPlayer.java, main() Teil 2

intPocket.set( 1 );
int x = intPocket.get();
stringPocket.set( "Selbstzerstörungsauslösungsschalterhintergrundbeleuchtung" );
String s = stringPocket.get();

Der Entwickler macht so im Programmcode sehr deutlich, dass die Taschen einen Integer enthalten und nichts anderes. Da Programmcode häufiger gelesen als geschrieben wird, sollten Autoren immer so viele Informationen wie möglich über den Kontext in den Programmcode legen. Zwar leidet die Lesbarkeit etwas, da insbesondere der Typ sowohl rechts wie auch links angegeben werden muss und die Syntax bei geschachtelten Generics lang werden kann, doch wie wir später sehen werden, lässt sich das noch abkürzen.

Das Schöne für die Typsicherheit ist, dass nun alle Eigenschaften mit dem angegebenen Typ geprüft werden. Wenn wir etwa aus intPocket mit get() auf das Element zugreifen, ist es vom Typ Integer (und durch Unboxing gleich int), und set() erlaubt auch nur ein Integer. Das macht den Programmcode robuster und durch den Wegfall der Typanpassungen kürzer und lesbarer.


Keine Primitiven Typparameter können in Java nur Java-Typen, also etwa Klassennamen sein, aber keine primitiven Datentypen. Das schränkt die Möglichkeiten zwar ein, doch da es Autoboxing gibt, lässt sich damit leben. Und wenn null in der Pocket<Integer> liegt, führt ein Unboxing zur Laufzeit zur NullPointerException.



Tabelle 7.1  Zusammenfassung der bisherigen Generics-Begriffe

Begriff Beispiel

generischer Typ (engl. generic type)

Pocket<T>

Typvariable oder formaler Typparameter (engl. formal type parameter)

T

parametrisierter Typ (engl. parameterized type)

Pocket<Long>

Typparameter (engl. actual type parameter)

Long

Originaltyp (engl. raw type)

Pocket


Geschachtelte Generics

Ist ein generischer Typ wie Pocket<T> gegeben, gibt es erst einmal keine Einschränkung für T. So beschränkt sich T nicht auf einfache Klassen- oder Schnittstellentypen, sondern kann auch wieder ein generischer Typ sein. Das ist logisch, denn jeder generische Typ ist ja ein eigenständiger Typ, der (fast) wie jeder andere Typ genutzt werden kann:

Listing 7.7  com/tutego/insel/generic/PocketPlayer.java, main() Teil 3

Pocket<Pocket<String>> pocketOfPockets = new Pocket<Pocket<String>>();
pocketOfPockets.set( new Pocket<String>() );
pocketOfPockets.get().set( "Inner Pocket<String>" );
System.out.println( pocketOfPockets.get().get() ); // Inner Pocket<String>

Hier enthält die Tasche eine Tasche, die eine Zeichenkette "Inner Pocket<String>" speichert.

Bei Dingen wie diesen ist schnell offensichtlich, wie hilfreich Generics für den Compiler (und uns) sind. Ohne Generics sähen eben alle Taschen gleich aus.


Tabelle 7.2  Übersichtlichkeit durch Generics

Präzise mit Generics Unpräzise ohne Generics

Pocket<String> stringPocket;

Pocket stringPocket;

Pocket<Integer> intPocket;

Pocket intPocket;

Pocket<Pocket<String>> pocketOfPockets;

Pocket pocketOfPockets;


Nur ein gut gewählter Name und eine präzise Dokumentation können bei nicht-generisch deklarierten Variablen helfen. Vor Java 5 haben sich Entwickler damit geholfen, mithilfe eines Blockkommentars Generics anzudeuten, etwa in Pocket/*<String>*/ stringPocket.


Rheinwerk Computing - Zum Seitenanfang

7.1.5 Generische Schnittstellen  Zur nächsten ÜberschriftZur vorigen Überschrift

Eine Schnittstelle kann genauso als generischer Typ deklariert werden wie eine Klasse. Werfen wir einen Blick auf die Schnittstellen java.lang.Comparable und java.util.Set, die beide seit Java 5 mit einer Typvariablen ausgestattet sind (das ? gestattet jeden Typparameter und soll uns in der Tabelle nicht irritieren).


public interface Comparable<T>
{
 public int compareTo(T o);
}
public interface Set<E> extends Collection<E>
{
 int size();
 boolean isEmpty();
 boolean contains(Object o);
 Iterator<E> iterator();
 Object[] toArray();
 <T> T[] toArray(T[] a);
 boolean add(E e);
 boolean remove(Object o);
 boolean containsAll(Collection<?> c);
 boolean addAll(Collection<? extends E> c);
 boolean retainAll(Collection<?> c);
 boolean removeAll(Collection<?> c);
 void clear();
 boolean equals(Object o);
 int hashCode();
}

Wie bekannt, greifen die Methoden auf die Typvariablen T und E zurück. Bei Set ist weiterhin zu erkennen, dass sie selbst eine generisch deklarierte Schnittstelle erweitert.

Beim Einsatz von generischen Schnittstellen lassen sich die folgenden zwei Benutzungsmuster ableiten:

  • Ein nicht-generischer Klassentyp löst Generics bei der Implementierung auf.
  • Ein generischer Klassentyp implementiert eine generische Schnittstelle und gibt die Parametervariable weiter.

Nicht-generischer Klassentyp löst Generics bei der Implementierung auf

Im ersten Fall implementiert eine Klasse die generisch deklarierte Schnittstelle und gibt einen konkreten Typ an. Alle numerischen Wrapper-Klassen implementieren zum Beispiel Comparable und füllen den Typparameter genau mit dem Typ der Wrapper-Klasse:

public final class Integer extends Number implements Comparable<Integer>
{
  public int compareTo( Integer anotherInteger ) { … }
  …

Durch diese Nutzung wird für den Anwender die Klasse Integer Generics-frei.

Generischer Klassentyp implementiert generische Schnittstelle und gibt die Parametervariable weiter

Die Schnittstelle Set schreibt Operationen für Mengen vor. Eine Klasse, die Set implementiert, ist zum Beispiel HashSet. Der Kopf der Typdeklaration ist folgender:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

Es ist abzulesen, dass Set eine Typvariable E deklariert, die HashSet nicht konkretisiert. Der Grund ist, dass die Datenstruktur Set vom Anwender als parametrisierter Typ verwendet wird und nicht aufgelöst werden soll.


Hinweis In manchen Situationen wird auch Void als Typparameter eingesetzt. Deklariert etwa interface I<T> { T foo(); } eine Typvariable T, doch gibt es bei der Implementierung von I nichts zurückzugeben, dann kann der Typparameter Void sein:

class C implements I<Void>
  @Override public Void foo() { return null;
}

Allerdings sind void und Void unterschiedlich, denn bei Void muss es eine Rückgabe geben, was ein return null notwendig macht.



Rheinwerk Computing - Zum Seitenanfang

7.1.6 Generische Methoden/Konstruktoren und Typ-Inferenz  topZur vorigen Überschrift

Die bisher genannten generischen Konstruktionen sahen im Kern wie folgt aus:

  • class C<T> {}
  • interface I<T> {}

Eine an der Klassen- oder Schnittstellendeklaration angegebene Typvariable gilt für alle nicht-statischen Methoden des Typs. Doch was machen wir, wenn

  • statische Methoden eine eigene Typvariable nutzen wollen?
  • diese Typvariable zu »global« ist und unterschiedliche Methoden unterschiedliche Typ-variablen nutzen möchten?

Eine Klasse kann auch ohne Generics deklariert werden, aber generische Methoden besitzen. Ganz allgemein kann jeder Konstruktor, jede Objektmethode und jede Klassenmethode einen oder mehrere formale Typparameter deklarieren. Sie stehen dann nicht mehr an der Klasse, sondern an der Methoden-/Konstruktordeklaration und sind »lokal« für die Methode beziehungsweise den Konstruktor. Das allgemeine Format ist:

Modifizierer <Typvariable(n)> Rückgabetyp Methodenname(Parameter) throws-Klausel

Ganz zufällig das eine oder andere Argument

Interessant sind generische Methoden insbesondere für Utility-Klassen, die nur statische Methoden anbieten, aber selbst nicht als Objekt vorliegen. Das folgende Beispiel zeigt das anhand einer Methode random():

Listing 7.8  com/tutego/insel/generic/GenericMethods.java, GenericMethods

public class GenericMethods
{
  public static <T> T random( T m, T n )
  {
    return Math.random() > 0.5 ? m : n;
  }

  public static void main( String[] args )
  {
    String s = random( "Analogkäse", "Gel-Schinken" );
    System.out.println( s );
  }
}

Es deklariert <T> T random(T m, T n) eine generische Methode, wobei der Rückgabetyp und Parametertyp durch eine Typvariable T bestimmt wird. Die Angabe von <T> beim Klassennamen ist bei dieser Syntax entfallen, und auf die Deklaration der Methode verschoben.


Hinweis Natürlich kann eine Klasse als generischer Typ und eine darin enthaltene Methode als generische Methode mit unterschiedlichem Typ deklariert werden. In diesem Fall sollten die Typvariablen unterschiedlich benannt sein, um den Leser nicht zu verwirren. So bezieht sich im Folgenden T bei sit() eben nicht auf die Parametervariable der Klasse Lupilu, sondern auf die der Methode:

class Lupilu<T> { <T> void sit( T val ); }  // Verwirrend
class Lupilu<T> { <V> void sit( V val ); }  // Besser

Der Compiler auf der Suche nach Gemeinsamkeiten

Den Typ (der wichtig für die Rückgabe ist) leitet der Compiler also automatisch aus dem Kontext, das heißt aus den Argumenten, ab. Diese Eigenschaft nennt sich Typ-Inferenz (engl type inference) Das hat weitreichende Konsequenzen.

Bei der Deklaration <T> T random(T m, T n) sieht es vielleicht auf den ersten Blick so aus, als ob die Variablentypen m und n absolut gleich sein müssen. Das stimmt aber nicht, denn bei den Typen geht der Compiler in der Typhierarchie so weit nach oben, bis er einen gemeinsamen Typ findet.


Tabelle 7.3  Gemeinsame Basistypen

Aufruf Identifizierte Typen Gemeinsame Basistypen

random("Essen", 1)

String, Integer

Object, Serializable, Comparable

random(1L, 1D)

Long, Double

Object, Number, Comparable

random(new Point(), new StringBuilder())

Point, StringBuffer

Object, Serializable, Cloneable


Es fällt auf, aber überrascht nicht, dass Object immer in die Gruppe gehört.

Die Schnittmenge der Typen bildet im Fall von random() die gültigen Rückgabetypen. Erlaubt sind demnach für die Parametertypen String und Integer:

Object       s1 = random( "Essen", 1 );
Serializable s2 = random( "Essen", 1 );
Comparable   s3 = random( "Essen", 1 );

Generische Methoden mit explizitem Typparameter *

Es gibt Situationen, in denen der Compiler nicht aus dem Kontext über Typ-Inferenz den richtigen Typ ableiten kann. Folgendes ist nicht möglich:

boolean hasPocket = true;
Pocket<String> pocket = hasPocket ? Pocket.newInstance() : null;

Der Eclipse-Compiler meldet "Type mismatch: cannot convert from Pocket<Object> to Pocket<String>".

Die Lösung: Wir müssen bei Pocket.newInstance() den Typparameter String explizit angeben:

Pocket<String> pocket = hasPocket ? Pocket.<String>newInstance() : null;

Die Syntax ist etwas gewöhnungsbedürftig, doch in der Praxis ist die explizite Angabe selten nötig.


Beispiel Ist das Argument der statischen Methode Arrays.asList() ein Feld, dann ist der explizite Typparameter nötig, da der Compiler nicht erkennen kann, ob das Feld selbst das eine Element der Rückgabe-List ist oder ob das Feld die Vararg-Umsetzung ist und alle Elemente des Feldes in die Rückgabeliste kommen:

List<String> list11 = Arrays.asList( new String[] { "A", "B" } );
List<String> list12 = Arrays.asList( "A", "B" );
System.out.println( list11 );   // [A, B]
System.out.println( list12 );   // [A, B]
List<String> list21 = Arrays.<String>asList( new String[] { "A", "B" } );
List<String> list22 = Arrays.<String>asList( "A", "B" );
System.out.println( list21 );   // [A, B]
System.out.println( list22 );   // [A, B]
List<String[]> list31 = Arrays.<String[]>asList( new String[] { "A", "B" } );
// List<String[]> list32 = Arrays.<String[]>asList( "A", "B" );
System.out.println( list31 );   // [[Ljava.lang.String;@69b332]

Zunächst gilt es, festzuhalten, dass die Ergebnisse für list11, list12, list21 und list22 identisch sind. Der Compiler setzt ein Vararg automatisch als Feld um und übergibt das Feld der asList()-Methode. Im Bytecode sehen daher die Aufrufe gleich aus (zur Wiederholung siehe Abschnitt 3.7.12, »Methode mit variabler Argumentanzahl (Vararg)«.)

Bei list21 und list22 ist der Typparameter jeweils explizit angegeben, aber nicht wirklich nötig, da ja das Ergebnis wie list11 bzw. list12 ist. Doch der Typparameter String macht deutlich, dass die Elemente im Feld, also die Vararg-Argumente, Strings sind. Spannend wird es bei list31. Zunächst zum Problem: Ist new String[]{"A", "B"} das Argument einer Vararg-Methode, so ist das mehrdeutig, weil genau dieses Feld das erste Element des vom Compiler automatisch aufgebauten Varargs-Feldes sein könnte (dann wäre es ein Feld im Feld) oder – und das ist die interne Standardumsetzung – der Java-Compiler das übergebene Feld als die Vararg-Umsetzung interpretiert. Diese Doppeldeutigkeit löst <String[]>, da in dem Fall klar ist, dass das von uns aufgebaute String-Feld das einzige Element eines neuen Varargs-Feldes sein muss. Und Arrays.<String[]>asList() stellt heraus, dass der Typ der Feldelemente String[] ist. Daher funktioniert auch die letzte Variablendeklaration nicht, denn bei asList("A", "B") ist der Elementtyp String, aber nicht String[].




Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
 <<   zurück
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Java SE Bibliotheken






 Java SE Bibliotheken


Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Einstieg in Eclipse






 Einstieg in
 Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in
 Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2011
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de