Galileo Computing < openbook > Galileo 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 Mathematisches
6 Eigene Klassen schreiben
7 Angewandte Objektorientierung
8 Exceptions
9 Die Funktionsbibliothek
10 Threads und nebenläufige Programmierung
11 Raum und Zeit
12 Datenstrukturen und Algorithmen
13 Dateien und Datenströme
14 Die eXtensible Markup Language (XML)
15 Grafische Oberflächen mit Swing
16 Grafikprogrammierung
17 Netzwerkprogrammierung
18 Verteilte Programmierung mit RMI und Web-Services
19 JavaServer Pages und Servlets
20 Applets
21 Midlets und die Java ME
22 Datenbankmanagement mit JDBC
23 Reflection und Annotationen
24 Logging und Monitoring
25 Sicherheitskonzepte
26 Java Native Interface (JNI)
27 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Stichwort

Download:
- ZIP, ca. 12,5 MB
Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren mit der Java Standard Edition Version 6
Buch: Java ist auch eine Insel

Java ist auch eine Insel
7., aktualisierte Auflage
geb., mit DVD (November 2007)
1.492 S., 49,90 Euro
Galileo Computing
ISBN 978-3-8362-1146-8
Pfeil 6 Eigene Klassen schreiben
Pfeil 6.1 Eigene Klassen mit Eigenschaften deklarieren
Pfeil 6.1.1 Attribute deklarieren
Pfeil 6.1.2 Methoden deklarieren
Pfeil 6.1.3 Die this-Referenz
Pfeil 6.2 Privatsphäre und Sichtbarkeit
Pfeil 6.2.1 Für die Öffentlichkeit: public
Pfeil 6.2.2 Paketsichtbar
Pfeil 6.2.3 Kein Public Viewing – Passwörter sind privat
Pfeil 6.2.4 Wieso nicht freie Methoden und Variablen für alle?
Pfeil 6.2.5 Privat ist nicht ganz privat: Es kommt darauf an, wer’s sieht
Pfeil 6.2.6 Zugriffsmethoden für Attribute deklarieren
Pfeil 6.2.7 Setter und Getter nach der JavaBeans-Spezifikation
Pfeil 6.3 Statische Methoden und statische Attribute
Pfeil 6.3.1 Warum statische Eigenschaften sinnvoll sind
Pfeil 6.3.2 Statische Eigenschaften mit static
Pfeil 6.3.3 Statische Eigenschaften über Referenzen nutzen?
Pfeil 6.3.4 Warum die Groß- und Kleinschreibung wichtig ist
Pfeil 6.3.5 Statische Eigenschaften und Objekteigenschaften
Pfeil 6.3.6 Statische Variablen zum Datenaustausch
Pfeil 6.3.7 Statische Blöcke als Klasseninitialisierer
Pfeil 6.4 Konstanten und Aufzählungen
Pfeil 6.4.1 Konstanten über öffentliche statische finale Variablen
Pfeil 6.4.2 Eincompilierte Belegungen der Klassenvariablen
Pfeil 6.4.3 Typ(un)sicherere Aufzählungen
Pfeil 6.4.4 Aufzählungen mit enum
Pfeil 6.5 Objekte anlegen und zerstören
Pfeil 6.5.1 Konstruktoren schreiben
Pfeil 6.5.2 Der Standard-Konstruktor
Pfeil 6.5.3 Parametrisierte und überladene Konstruktoren
Pfeil 6.5.4 Konstruktor nimmt ein Objekt vom eigenen Typ an (Copy-Konstruktor)
Pfeil 6.5.5 Einen anderen Konstruktor der gleichen Klasse aufrufen
Pfeil 6.5.6 Initialisierung der Objekt- und Klassenvariablen
Pfeil 6.5.7 Finale Werte im Konstruktor und in statischen Blöcken setzen
Pfeil 6.5.8 Exemplarinitialisierer (Instanzinitialisierer)
Pfeil 6.5.9 Ihr fehlt uns nicht – der Garbage-Collector
Pfeil 6.5.10 Implizit erzeugte String-Objekte
Pfeil 6.5.11 Private Konstruktoren, Utility-Klassen, Singleton, Fabriken
Pfeil 6.6 Assoziationen zwischen Objekten
Pfeil 6.6.1 Unidirektionale 1:1-Beziehung
Pfeil 6.6.2 Bidirektionale 1:1-Beziehungen
Pfeil 6.6.3 Unidirektionale 1:n-Beziehung
Pfeil 6.7 Vererbung
Pfeil 6.7.1 Vererbung in Java
Pfeil 6.7.2 Spielobjekte modelliert
Pfeil 6.7.3 Einfach- und Mehrfachvererbung
Pfeil 6.7.4 Sichtbarkeit protected
Pfeil 6.7.5 Konstruktoren in der Vererbung und super
Pfeil 6.7.6 Automatische und explizite Typanpassung
Pfeil 6.7.7 Das Substitutionsprinzip
Pfeil 6.7.8 Typen mit dem binären Operator instanceof testen
Pfeil 6.7.9 Methoden überschreiben
Pfeil 6.7.10 Mit super an die Eltern
Pfeil 6.7.11 Kovariante Rückgabetypen
Pfeil 6.7.12 Array-Typen und Kovarianz
Pfeil 6.7.13 Zusammenfassung zur Sichtbarkeit
Pfeil 6.8 Dynamisches Binden
Pfeil 6.8.1 Unpolymorph bei privaten, statischen und finalen Methoden
Pfeil 6.8.2 Polymorphie bei Konstruktoraufrufen
Pfeil 6.8.3 Finale Klassen
Pfeil 6.8.4 Nicht überschreibbare (finale) Methoden
Pfeil 6.9 Abstrakte Klassen und abstrakte Methoden
Pfeil 6.9.1 Abstrakte Klassen
Pfeil 6.9.2 Abstrakte Methoden
Pfeil 6.10 Schnittstellen
Pfeil 6.10.1 Deklarieren von Schnittstellen
Pfeil 6.10.2 Implementieren von Schnittstellen
Pfeil 6.10.3 Markierungsschnittstellen
Pfeil 6.10.4 Ein Polymorphie-Beispiel mit Schnittstellen
Pfeil 6.10.5 Die Mehrfachvererbung bei Schnittstellen
Pfeil 6.10.6 Keine Kollisionsgefahr bei Mehrfachvererbung
Pfeil 6.10.7 Erweitern von Interfaces – Subinterfaces
Pfeil 6.10.8 Vererbte Konstanten bei Schnittstellen
Pfeil 6.10.9 Schnittstellenmethoden, die nicht implementiert werden müssen
Pfeil 6.10.10 Abstrakte Klassen und Schnittstellen im Vergleich
Pfeil 6.11 Geschachtelte (innere) Klassen, Schnittstellen, Aufzählungen
Pfeil 6.11.1 Statische innere Klassen und Schnittstellen
Pfeil 6.11.2 Mitglieds- oder Elementklassen
Pfeil 6.11.3 Lokale Klassen
Pfeil 6.11.4 Anonyme innere Klassen
Pfeil 6.11.5 this und Vererbung
Pfeil 6.12 Generische Datentypen
Pfeil 6.12.1 Einfache Klassenschablonen
Pfeil 6.12.2 Einfache Methodenschablonen
Pfeil 6.12.3 Umsetzen der Generics, Typlöschung und Raw-Types
Pfeil 6.12.4 Einschränken der Typen
Pfeil 6.12.5 Generics und Vererbung, Invarianz
Pfeil 6.12.6 Wildcards
Pfeil 6.13 Die Spezial-Oberklasse Enum
Pfeil 6.13.1 Methoden auf Enum-Objekten
Pfeil 6.13.2 enum mit eigenen Konstruktoren und Methoden
Pfeil 6.14 Dokumentationskommentare mit JavaDoc
Pfeil 6.14.1 Einen Dokumentationskommentar setzen
Pfeil 6.14.2 Mit javadoc eine Dokumentation erstellen
Pfeil 6.14.3 HTML-Tags in Dokumentationskommentaren
Pfeil 6.14.4 Generierte Dateien
Pfeil 6.14.5 Dokumentationskommentare im Überblick
Pfeil 6.14.6 JavaDoc und Doclets
Pfeil 6.14.7 Veraltete (deprecated) Klassen, Konstruktoren und Methoden


Galileo Computing - Zum Seitenanfang

6.12 Generische Datentypen Zur nächsten ÜberschriftZur vorigen Überschrift

Nehmen wir an, wir wollten einen Container für eine einfache Zahl implementieren; der Datentyp soll int sein:

class IntBox 
{ 
  private int val; 
 
  void setValue( int val ) 
  { 
    this.val = val; 
  } 
 
  int getValue() 
  { 
    return val; 
  } 
}

Es gibt einen automatischen Standard-Konstruktor, und mit setValue() können wir eine Zahl angeben und den Wert über die Zugriffsfunktion auslesen. Was ist, wenn wir eine zweite Box für Strings implementieren wollen? Wir müssten Quellcode duplizieren und überall, wo int steht, den Datentyp String einsetzen:

class StringBox 
{ 
  private String val; 
 
  void setValue( String val ) 
  { 
    this.val = val; 
  } 
 
  String getValue() 
  { 
    return val; 
  } 
}

In unserem Beispiel sind das drei Änderungen, aber der Rest des Gerüstes bleibt gleich. Für jeden neuen Datentyp muss eine neue Klasse her – die Logik bleibt die gleiche. Es gibt eine ganze Reihe von Beispielen, in denen Speicherstrukturen wie unsere Box nicht nur für einen Datentyp sinnvoll sind. Das gilt auch für Algorithmen, etwa einen, der Zahlen sortiert. Wenn zwei Zahlen größer oder kleiner sein können, muss ein Algorithmus lediglich diese Eigenschaft nutzen können, aber es wäre egal, ob die Zahlen von der Größe byte, short, int, long oder auch Gleitkommazahlen sind – der Algorithmus selbst ist davon nicht betroffen.

Ein Algorithmus, der von einem Datentyp unabhängig programmiert werden kann, heißt generisch, und die Möglichkeit, in Java mit generischen Typen zu arbeiten, heißt Generics. [In C(++) werden diese Typen von Klassen parametrisierte Klassen oder Templates (Schablonen) genannt. ] Leider funktioniert das Beispiel mit der IntBox in Java nicht, denn generische Typen können in Java nur Objekte sein, aber keine primitiven Datentypen. Das schränkt die Möglichkeiten zwar ein, doch weil es Autoboxing gibt, lässt sich damit leben.

Die Implementierung der Java-Generics basiert auf einem Projekt namens Pizza. Später wurde aus Pizza GJ, und schließlich wurde dies zur Basis des Java Specification Request 14: »Add Generic Types To The Java Programming Language«.


Galileo Computing - Zum Seitenanfang

6.12.1 Einfache Klassenschablonen Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn wir die StringBox als Basis nehmen, haben wir bei der Übersetzung der IntBox in die StringBox drei Stellen gefunden, an denen wir den Typ ändern müssen. Wollen wir die Box in einen generischen Typ umbauen, so müssen wir an den drei Stellen, an denen der konkrete Typ String vorkam, einen Typ-Stellvertreter, eine so genannte Typ-Variable, einsetzen. Der Name der Typ-Variable muss in der Klassendeklaration angegeben werden, da es durchaus mehr als einen Stellvertreter geben kann. Die Syntax für die generische Typdefinition für unsere Box ist folgende:

Listing 6.101 com/tutego/insel/generics/Box.java

package com.tutego.insel.generics;

class Box<T> 
{ 
  private T val; 
 
  void setValue( T val ) 
  { 
    this.val = val; 
  } 
 
  T getValue() 
  { 
    return val; 
  } 
}

An Stelle des konkreten Typs String steht ein formaler Typ-Parameter, in unserem Fall ist das T – die Abkürzung ist oft T, das für Typ steht. Bei generischen Klassen steht die Angabe des Typnamens nur einmal zu Beginn der Klassendeklaration in spitzen Klammern hinter dem Klassennamen.

Um die Box nutzen zu können, müssen wir sie mit einem speziellen Typ erzeugen, dem parametrisierten Typ. Er ist eine Instanziierung eines generischen Typs mit konkreten Typargumenten (etwa String oder Integer oder Point). Der konkrete Typ steht hinter dem Klassennamen in spitzen Klammern.

Listing 6.102 com/tutego/insel/generics/GenericBoxExample1.java, Ausschnitt

Box<String>  stringBox = new Box<String>(); 
Box<Integer> intBox    = new Box<Integer>(); 
Box<Point>   pointBox  = new Box<Point>();

Das Schöne daran ist, dass nun alle generischen Eigenschaften mit dem angegebenen Typ durchgeführt werden. Wenn wir etwa aus pointBox mit getValue() auf das Element zugreifen, ist es vom Typ Point:

pointBox.setValue( new Point(1, 2) ); 
double x = pointBox.getValue().getX();

Galileo Computing - Zum Seitenanfang

6.12.2 Einfache Methodenschablonen Zur nächsten ÜberschriftZur vorigen Überschrift

Eine Klasse kann ganz normal ohne Generics deklariert werden, aber mit Methoden, die die Typen generisch vorschreiben. Das gilt für Objektmethoden und Klassenmethoden. Interessant ist dies für Utility-Klassen, die nur statische Funktionen anbieten, aber selbst nicht als Objekt vorliegen.

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

Hier wird die Funktion random() auf einem beliebigen Typ deklariert; die Angabe von <T> beim Klassennamen entfällt und verschiebt sich auf die Deklaration der Methode.

Die Benutzung kann ohne Angabe des Typs – aber doch überprüft – geschehen:

String s = Util.random( "Essen", "Schlafen" );

Ersetzen wir einen der beiden Strings durch einen Wert vom Typ Integer, so wird der Compiler einen Fehler melden, da die beiden übergebenen Typen nicht übereinstimmen.


Galileo Computing - Zum Seitenanfang

6.12.3 Umsetzen der Generics, Typlöschung und Raw-Types Zur nächsten ÜberschriftZur vorigen Überschrift

Es gibt zwei Realisierungsmöglichkeiten von generischen Datentypen:

  • Heterogene Variante. Für jeden Typ (etwa String, Integer, Point) wird individueller Code erzeugt, also drei Klassen.
  • Homogene Übersetzung. Für jede parametrisierte Klasse wird eine Klasse erzeugt, die anstelle des generischen Typs Object einsetzt. Für einen konkreten Typ werden Typanpassungen in die Anweisungen eingebaut.

Java nutzt die homogene Übersetzung.

Typlöschung

Übersetzt der Java-Compiler die generischen Anwendungen, so löscht er dabei alle Typinformationen (engl. typ erasure). Das ist zwar für die Laufzeitumgebung praktisch, weil sie nicht auf die Generics großartig angepasst werden muss, sonst aber stellt das ein riesiges Problem dar, weil dann die Typinformationen zur Laufzeit nicht vorhanden sind. Programmierer müssen das wissen, da zum Beispiel der instanceof-Operator so nicht funktioniert:

Listing 6.103 com/tutego/insel/generics/GenericBoxExample2.java, Ausschnitt

Box<String> stringBox = new Box<String>(); 
 
boolean b1 =  stringBox instanceof Box<Number>; // illegal generic type for instanceof 
boolean b2 =  stringBox instanceof Box<String>; // illegal generic type for instanceof

Der Compiler meldet zu Recht einen Fehler – nicht nur eine Warnung –, weil es den Typ Box<String> und Box<Number> zur Laufzeit gar nicht gibt: Es sind nur typgelöschte Box-Objekte. Das zeigt auch das folgende Beispiel:

Listing 6.104 com/tutego/insel/generics/GenericBoxExample2.java, Ausschnitt

Box<String>  box1 = new Box<String>(); 
Box<Integer> box2 = new Box<Integer>(); 
System.out.println( box1.getClass() == box2.getClass() );     // true

Dass diese Informationen übrigens nicht vorliegen, wird auch damit begründet, dass die Laufzeit leiden könnte. Microsoft war das aber egal, dort besteht Generizität in der Common Language Runtime (CLR). Microsoft ist damit einen klaren Schritt voraus.

Typanpassungen

Ein mit Generics spezifizierter Typ lässt sich durch Typanpassung auf eine allgemeine Form bringen:

List<Integer> list = new ArrayList<Integer>(); 
List noGenericList = (List) list;

Bei der noGenericList prüft der Compiler natürlich gar keine Typen mehr, denn er hat sie ja »vergessen«. Eine Anweisung, die einen Nicht-Integer-Typ in die Liste einfügt, bringt keinen Fehler zur Übersetzungszeit, und so kann über die Hintertür ein falscher Typ in die Datenstruktur kommen:

noGenericList.add( "Das macht man aber nicht" );

Die Generics bieten uns Möglichkeiten, den Quellcode sicherer zu machen. Wir sollten diese Sicherheit nicht durch ungetypte Schreibweisen kaputtmachen.

Raw-Type

Generische Klassen müssen nicht unbedingt parametrisiert werden; sie sind mit dem Datentyp Object weiterhin gültig. Das ist auch wichtig, da sonst viele parametrisierte neue Klassen nicht mehr mit altem Programmcode verwendet werden könnten.

Listing 6.105 com/tutego/insel/generics/GenericBoxExample3.java, Ausschnitt

Box stringBox = new Box<String>(); 
Box pointBox  = new Box<Point>(); 
Box objectBox = new Box();

Ein generischer Typ ohne Typargument heißt Raw-Type. In unserem Beispiel ist Box der Raw-Type von Box<T>. Ein Raw-Type besitzt die gleichen Eigenschaften wie ein generischer Typ, nur kann der Compiler ohne parametrisierten Typ die Typkonformität nicht mehr prüfen. Im ersten Fall liefert getValue() den korrekten Typ String, Analoges gilt für Point. Im letzten Fall liefert getValue() nur Object, da hier jeder Bezug zum Typ fehlt.

Doch was ist eigentlich mit setValue()? Erinnern wir uns an die Deklaration:

void setValue( T val ) 
{ 
  this.val = val; 
}

Diese Funktion ist so ausgelegt, dass sie ein Argument von mindestens dem Typ akzeptiert, mit dem sie parametrisiert wurde. Fehlt durch die Verwendung des Raw-Typs der konkrete Typ, bleibt Object, und der Compiler gibt eine Warnung aus:

objectBox.setValue( "Unsafe type operation: " + 
    "Should not invoke the method setValue(T) of raw type Box. " + 
    "References to generic type Box<T> should be parameterized" );

Der Hinweis besagt, dass die Box typisiert hätte werden müssen. Achten wir darauf nicht, kann das schnell zu Problemen führen:

Box<String> stringBox2 = new Box<String>(); 
Box         objectBox2 = stringBox2;

Der Compiler gibt keinen Fehler und auch keine Warnung aus. Die zweite Zeile ist allerdings hochgradig problematisch, denn über die nicht parametrisierte Objekt-Box können wir beliebige Objekte in die Box packen. Da aber objectBox2 in Wirklichkeit ein typgelöschter Verweis auf die stringBox2 ist, haben wir ein Typproblem:

objectBox2.setValue( new java.util.Date() ); // Warnung vor der "Unsafe type  
operation" 
String s = stringBox2.getValue(); 
System.out.println( stringBox2.getValue() );

Die letzten beiden Zeilen führen zur Laufzeit zu einer ClassCastException der Art:

Exception in thread "main" java.lang.ClassCastException: java.util.Date 
  at com.tutego.insel.generics.GenericBoxExample3.main(GenericBoxExample3.java:17)

Galileo Computing - Zum Seitenanfang

6.12.4 Einschränken der Typen Zur nächsten ÜberschriftZur vorigen Überschrift

Bei generischen Angaben können die Typen weiter eingeschränkt werden. Es kann vorgeschrieben werden, dass der Typ eine konkrete Schnittstelle implementieren muss. Unsere Deklaration von random() sah keine Einschränkungen vor, sodass zum Beispiel auch Folgendes möglich ist:

Serializable s1 = new Serializable() { }; 
Serializable s2 = new Point(); 
Util.random( s1, s2 );

Da der Typ beliebig ist, können auch Objekte aufgenommen werden, die vielleicht wenig Sinn ergeben, wie unsere Klassen, die lediglich Serializable implementieren.

Einschränkung mit extends

Um dies zu vermeiden, kann der Typ bei der Deklaration eingeschränkt werden. Wollen wir nur Klassen, die CharSequence implementieren (also Zeichenfolgen wie String und StringBuffer/StringBuilder), so schreiben wir das in die Deklaration mit hinein:

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

Jetzt wird der Compiler mit übergebenem Serializable einen Fehler melden. Einen Aufruf wie Util.random("Kino", "Lesen"); lässt der Compiler korrekterweise durch.

Sinnvoll ist diese Einschränkung, wenn es Schnittstellen sind, die Methoden vorschreiben, da sich diese direkt nutzen lassen. Das ist logisch, denn bei einer Einschränkung des Typs wird der Compiler sicherstellen, dass die konkreten Typen die vorgeschriebene Schnittstelle haben und damit die Methoden existieren.

Nehmen wir an, wir wollten ein typsicheres max() implementieren. Es soll den größeren der beiden Werte zurückgeben. Vergleiche lassen sich einfach tätigen, wenn die Objekte Comparable implementieren, denn compareTo() liefert einen Rückgabewert, der aussagt, welches Objekt nach der definierten Metrik kleiner, größer oder gleich ist.

public static <T extends Comparable> T max( T m, T n ) 
{ 
  return m.compareTo( n ) > 0 ? m : n; 
}

Die Nutzung ist einfach:

System.out.println( Util.max( "Kino", "Lesen" ) );                    // Lesen 
System.out.println( Util.max( new Integer(12), new Integer(100) ) );  // 100

Einschränkung mit super

Neben extends kann der Typ auch mit super eingeschränkt werden. Dann sind nicht mehr alle Untertypen erlaubt, sondern der genannte Typ bestimmt den maximalen Obertyp. Während also <T extends CharSequence> eine Typisierung mit Klassen ermöglicht, die CharSequence implementieren, also mindestens vom Typ CharSequence sind, würde <T super String> die Typen einschließen, die in der Vererbungshierarchie unter String liegen. Die Einschränkung ist wenig nötig und im Allgemeinen für Wildcards sinnvoll, die wir gleich kennenlernen werden.

Weitere Obertypen mit &

Soll der konkrete Typ zu mehreren Typen passen, lassen sich mit einem & weitere Obertypen hinzunehmen. Wichtig ist aber, dass nur eine Klassen-Vererbungsangabe stattfinden kann, also nur ein extends stehen darf, da Java keine Mehrfachvererbung auf Klassenebene unterstützt. Der Rest müssen implementierte Schnittstellen sein.

Nehmen wir eine Oberklasse O und Schnittstellen I1 und I2 an. Dann sind die folgenden Deklarationen erlaubt:

  • <T extends O>
  • <T extends I1 & I2>
  • <T extends O & I1>
  • <T extends O & I1 & I2>

Falsch wäre etwa <T extends O1 & T extends O2>.


Galileo Computing - Zum Seitenanfang

6.12.5 Generics und Vererbung, Invarianz Zur nächsten ÜberschriftZur vorigen Überschrift

Im Zusammenhang mit Generics und Vererbung muss beachtet werden, dass die übliche Substitution nicht funktioniert. Wenn Player eine Unterklasse von GameObject ist, so ist Box<Player> keine Unterklasse von Box<GameObject>, genauso wie Box<Object> nicht die Oberklasse von Box<GameObject> ist beziehungsweise eine Box, die alle erdenklichen Typen ermöglicht.

Der Compiler meckert bei diesem Versuch sofort:

Box<Object> box; 
box = new Box<GameObject>();      // incompatible types

Die Erklärung ist schnell an einem Beispiel gezeigt. Da nun die Box in setValue() alles vom Typ Object aufnimmt – und ein String ist ein Object –, scheint folgender Argumenttyp zu funktionieren:

box.setValue( "Invarianz" );

Doch da die Box tatsächlich mit Spielobjekten aufgebaut wurde, passt der String hier nicht hinein.

Die Ableitungsbeziehung zwischen Typargumenten überträgt sich folglich nicht auf generische Klassen. Das wäre ein Fehler, und daher gibt es keine Kovarianz bei Generics; die Tatsache nennt sich Invarianz. Bei Funktionsaufrufen ist das besonders lästig, doch werden wir später mit den Wildcards eine Lösung kennenlernen. Es wird damit

void out( Box<GameObject> g )

nur eine Box mit GameObject aufnehmen können, aber zum Beispiel keine Box<Player>.


Galileo Computing - Zum Seitenanfang

6.12.6 Wildcards topZur vorigen Überschrift

Ein bewusstes »Vergessen« der Typinformationen lässt sich durch das Wildcard ? erreichen. Es erlaubt aber auch, verschiedene Unterklassen zusammenzuführen. Wir haben schon oben gesehen, dass etwa Folgendes nicht funktioniert:

Listing 6.106 com/tutego/insel/generics/GenericBoxExample4.java, Ausschnitt

Box<Number>  numberBox; 
Box<Integer> bI = new Box<Integer>(); 
Box<Double>  bD = new Box<Double>(); 
numberBox = bI;                           // Compilerfehler Type mismatch

Über Wildcards ist es jedoch möglich, den Typ von numberBox frei zu lassen, was jetzt aber nicht so gemeint ist, dass wir die Box gänzlich ohne generische Syntax deklarieren, sondern als:

Box<?> b;

Das Fragezeichen in der Angabe steht für alle Boxen und für alle Untertypen T im Fall von Box<T>. Es ist wichtig zu verstehen, dass ? nicht für Object steht, sondern für einen unbekannten Typ!

Da die Wildcards auch mit Typeinschränkung arbeiten, können wir auf diese Weise eine Box von Number-Objekten aufbauen:

Box<? extends Number> numberBox; 
Box<Integer> intBox    = new Box<Integer>(); 
Box<Double>  doubleBox = new Box<Double>(); 
Box<String>  stringBox = new Box<String>();

Die Schreibweise <? extends Klasse> nennt sich upper bound wildcard. (Eine Einschränkung der Art <? super Klasse> heißt lower bound wildcard.) So funktioniert die Zuweisung:

numberBox = intBox;           // oder numberBox = doubleBox;

Aber nicht so:

numberBox = stringBox;        // Type mismatch

Die Nutzung der numberBox ist jedoch insofern eingeschränkt, als das setValue() nur über die konkrete Box, also etwa intBox, funktioniert, aber nicht über die allgemeine numberBox. Der Zugriff ist unproblematisch.

numberBox.setValue( 1 );      // Compilerfehler 
intBox.setValue( 1 ); 
Number number = numberBox.getValue(); 
System.out.println( number ); // 1

Weitere Anwendungen der upper bound wildcard und lower bound wildcard beschreibt das Kapitel 12 über Datenstrukturen.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






<< zurück



Copyright © Galileo Press 2008
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.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de