| author | Carsten Gips (HSBI) |
|---|---|
| title | Generics2: Bounds & Wildcards |
::: tldr
Typ-Parameter können durch Bounds eingeschränkt werden: <T extends ...>
bedeutet, dass der Typ-Parameter T nach oben eingeschränkt wird ("upper bound").
Durch extends-Bounds des Typ-Parameters können in der generischen Klasse/Methode
dann alle Methoden der Schranke (Bound) verwendet werden.
Ein Wildcard (?) steht für einen unbestimmten Typ. Ein Wildcard-Typ hat keinen
Namen / ist nicht benennbar und ist innerhalb der Klasse/Methode nicht direkt
zugreifbar. Wildcards können mit ? extends ... nach oben ("upper bound") oder
? super ... nach unten ("lower bound") eingeschränkt werden.
Bei der Nutzung mit ? extends Bound muss der konkrete Typ die Schranke selbst oder
ein Subtyp davon sein. Analog muss bei der Nutzung mit ? super Bound der konkrete
Typ die Schranke selbst oder ein Supertyp (Obertyp) der angegebenen Schranke sein.
:::
::: youtube Vorlesung [YT], [HSBI]
Demo Wildcards [YT], [HSBI] :::
public class Cps<E extends Number> {
// Obere Schranke: E muss Number oder Subklasse sein
// => Zugriff auf Methoden aus Number moeglich
}
Cps<Double> a;
Cps<Number> b;
Cps<String> c; // Fehler!!!\bigskip \smallskip
-
Schlüsselwort
extendsgilt hier auch für Interfaces -
Mehrere Interfaces: nach
extendseine Klasse oder ein Interface, danach mit "&" getrennt die restlichen Interfaces:class Cps<E extends KlasseOderInterface & I1 & I2 & I3> {}
:::: notes
Falls eine Klasse mehreren Obertypen folgen soll, können mehrere Bound-Typen durch
& verbunden werden. Der erste Bound kann eine Klasse (z.B. Number) sein; alle
weiteren Bound-Typen müssen Interfaces sein. Wenn kein Klassen-Bound existiert,
können alle Bound-Typen Interfaces sein.
::: tip
Anmerkung: Der Typ-Parameter ist nur mit extends nach oben beschränkbar. Für
die Wildcards gibt es zusätzlich auch noch die Beschränkung mit super nach unten.
:::
[Beispiel bounds.Cps]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/bounds/Cps.java"} ::::
\bigskip
::: center
Wildcard mit "?" => steht für unbestimmten Typ
:::
\bigskip
public class Wuppie {
public void m1(List<?> a) { ... }
public void m2(List<? extends Number> b) { ... }
}-
m1:Listbeliebig parametrisierbar\newline{=tex} => Inm1für Objekte in Listeanur Methoden vonObjectnutzbar! -
m2:Listmuss mitNumberoder Subklasse parametrisiert werden.\newline{=tex} => Dadurch für Objekte in Listeballe Methoden vonNumbernutzbar ...
::: notes
Die Wildcard ? steht für einen unbekannten Typ. List<?> erlaubt List-Objekte
jedes Typs, aber innerhalb der Methode kann man nicht sicher auf spezifische
Eigenschaften des konkreten Typs zugreifen. Wichtig: List<?> ist also
nicht eine "Liste von Object", sondern eine Liste von "unbekanntem Typ".
- Typvariable: "ich benenne den Typ und kann ihn mehrfach verwenden"
- Wildcard: "ich akzeptiere etwas Unbekanntes, kann es aber nicht benennen"
Mit List<? extends A> erlaubt man Listen von Elementen, die A oder eine
Unterklasse von A sind (kovariant, siehe auch Diskussion in "Generics3:
Generics und Polymorphie"); man kann Elemente als A
lesen/nutzen, aber nicht sicher als A der Liste hinzufügen (weil der echte Typ wg.
des Wildcards unbekannt ist - es könnte ein beliebiger Untertyp von A sein).
Weitere Eigenschaften:
- Durch Wildcard kein Zugriff auf den Typ
- Wildcard kann durch upper bound oder lower bound eingeschränkt werden
- Geht nicht bei Klassen-/Interface-Definitionen, hier wird eine Typ-Variable benötigt
Weitere Beispiele:
List<?>- Typ der Listenelemente unbekannt, nur Methoden vonObjectnutzbarList<? extends T>- Typ der Listenelemente istToder eine Unterklasse vonT; Zugriff lesend mit den Methoden vonT(außernull), Schreiben nur eingeschränkt möglich (konkreter Typ unklar wg.?- es könnte eine Unterklasse vonTsein, und Schreiben von Elementen vom TypTwürde (wenn es erlaubt wäre) dann zur Laufzeit schief gehen - Java fängt das aber zur Compilezeit ab)List<? super T>- Typ der Listenelemente istToder eine Oberklasse vonT; Zugriff schreibend möglich mit Werten vom TypTund Untertypen, Lesen nur mitObject(der konkrete Typ samt Schnittstelle ist unklar: es könnte eine beliebige Oberklasse vonTsein, die eine völlig unterschiedliche Schnittstelle alsThat)
=> Das soll uns als erste Einführung von Bounds und Wildcards reichen. Wir werden
überwiegend die extends-Bounds verwenden. Für eine genauere Diskussion von "Type
Erasure" (TE) und "Producer extends, Consumer super" (PECS-Regel) sowie die
Varianz-Diskussion siehe Lektion "Generics3: Generics und
Polymorphie".
:::
\bigskip \bigskip
@Bloch2018: Wildcards meist für Parameter; Rückgabewerte möglichst konkret typisieren.
::: notes Generische Typen in Rückgabewerten sollten möglichst konkrete Typen oder Bounds verwenden, um Typ-Sicherheit zu wahren. :::
Ausgabe für Listen gesucht, die sowohl Elemente der Klasse A als auch Elemente der
Klasse B enthalten [können]{.notes}
\bigskip
class A { void printInfo() { System.out.println("A"); } }
class B extends A { void printInfo() { System.out.println("B"); } }
public class X {
public static void main(String[] args) {
List<A> x = new ArrayList<A>();
x.add(new A()); x.add(new B());
printInfo(x); // Klassenmethode in X, gesucht
List<B> y = new ArrayList<B>();
y.add(new B()); y.add(new B());
printInfo(y); // Klassenmethode in X, gesucht
}
}::: notes Hinweis: Dieses Beispiel berührt auch Polymorphie bei/mit generischen Datentypen, vgl. dritter Teil "Generics und Polymorphie" anschauen. :::
::: notes
public class X {
public static void printInfo(List<A> list) {
for (A a : list) { a.printInfo(); }
}
}=> So geht's nicht! Eine List<B> ist keine List<A> (auch wenn ein B
ein A ist, vgl. spätere Sitzung zu Generics und Vererbung ...)!
[Beispiel wildcards.v1.X]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/wildcards/v1/X.java"}
public class X {
public static void printInfo(List<?> list) {
for (Object a : list) { a.printInfo(); }
}
}=> So gehts auch nicht! Im Prinzip passt das jetzt für List<A> und List<B>.
Dummerweise hat man durch das Wildcard keinen Zugriff mehr auf den Typ-Parameter und
muss für den Typ der Laufvariablen in der for-Schleife dann Object nehmen. Aber
Object kennt unser printInfo nicht ... Außerdem könnte man die Methode
X#printInfo dank des Wildcards auch mit allen anderen Typen aufrufen ...
[Beispiel wildcards.v2.X]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/blob/master/lecture/java-classic/src/wildcards/v2/X.java"}
public class X {
public static void printInfo(List<? extends A> list) {
for (A a : list) { a.printInfo(); }
}
}Das ist die Lösung. Man erlaubt als Argument nur List-Objekte und fordert, dass
sie mit A oder einer Unterklasse von A parametrisiert sind. D.h. in der Schleife
kann man sich auf den gemeinsamen Obertyp A abstützen und hat dann auch wieder die
printInfo-Methode (von A) zur Verfügung ...
:::
[Konsole wildcards.v3.X]{.ex href="https://github.com/Programmiermethoden-CampusMinden/Prog2-Lecture/tree/master/lecture/java-classic/src/wildcards/v3"}
- Ein Wildcard (
?) als Typ-Parameter steht für einen beliebigen Typ- Ist in Klasse oder Methode dann aber nicht mehr zugreifbar
\smallskip
- Mit Bounds kann man Typ-Parameter/Wildcards nach oben oder nach unten
einschränken (im Sinne einer Vererbungshierarchie)
extends: Der Typ-Parameter bzw. die Wildcard muss eine Unterklasse eines bestimmten Typen seinsuper: Die Wildcard muss eine Oberklasse eines bestimmten Typen sein
::: readings Lesen Sie zu diesem Thema auch in den Oracle-Tutorials "Wildcards" und "More Fun with Wildcards" sowie im dev.java-Tutorial "Wildcards" nach. :::
::: outcomes
- k3: Ich kann Wildcards und Bounds bei generischen Klassen/Methoden einsetzen :::
::: challenges Spieler, Mannschaften und Ligen Modellieren Sie in Java verschiedene Spielertypen sowie generische Mannschaften und Ligen, die jeweils bestimmte Spieler (-typen) bzw. Mannschaften aufnehmen können.
-
Implementieren Sie die Klasse
Spieler, die das InterfaceISpielererfüllt.public interface ISpieler { String getName(); }
-
Implementieren Sie die beiden Klassen
FussballSpielerundBasketballSpielerund sorgen Sie dafür, dass beide Klassen vom Compiler als Spieler betrachtet werden (geeignete Vererbungshierarchie). -
Betrachten Sie das nicht-generische Interface
IMannschaft. Erstellen Sie daraus ein generisches InterfaceIMannschaftmit einer Typ-Variablen. Stellen Sie durch geeignete Beschränkung der Typ-Variablen sicher, dass nur Mannschaften mit vonISpielerabgeleiteten Spielern gebildet werden können.public interface IMannschaft { boolean aufnehmen(ISpieler spieler); boolean rauswerfen(ISpieler spieler); }
-
Betrachten Sie das nicht-generische Interface
ILiga. Erstellen Sie daraus ein generisches InterfaceILigamit einer Typvariablen. Stellen Sie durch geeignete Beschränkung der Typvariablen sicher, dass nur Ligen mit vonIMannschaftabgeleiteten Mannschaften angelegt werden können.public interface ILiga { boolean aufnehmen(IMannschaft mannschaft); boolean rauswerfen(IMannschaft mannschaft); }
-
Leiten Sie von
ILigadas generische InterfaceIBundesLigaab. Stellen Sie durch geeignete Formulierung der Typvariablen sicher, dass nur Ligen mit Mannschaften angelegt werden können, deren Spieler vom TypFussballSpieler(oder abgeleitet) sind.Realisieren Sie nun noch die Funktionalität von
IBundesLigaals nicht-generisches InterfaceIBundesLiga2. :::