Generics¶
Ein generischer Typ (generic type) ist eine Klasse oder ein Interface, die mit einem oder mehreren Typen parametrisiert ist. Wir kennen die Anwendung von Generics bereits aus Collections. So sind z.B. die Typen List
und Set
mit dem generischen Typen E
parametrisiert. Generell gilt also List<E>
und Set<E>
, wobei E
für einen beliebigen (Referenz-)Typen für die Elemente in der Liste bzw. der Menge stehen. Den konkreten Typ der Elemente gibt man dann bei der Deklaration an:
List<String> words = new ArrayList<>(); // Liste, die Strings enthaelt
Set<Integer> numbers = new HashSet<>(); // menge, die Integer enthaelt
Wir zeigen hier nun, wie wir uns eine eigene generische Klasse schreiben können:
Wir parametrisieren im Klassenkopf die Klasse MyGenericClass
einfach mittels <T>
mit einem Typen und verwenden diesen Platzhalter T
überall dort, wo der Typ verwendet wird, z.B. bei der Deklaration der Objektvariablen value
, beim Parameter im Konstruktor und beim Rückgabetyp des Getters.
Bei der Objekterzeugung kann nun jeder beliebige (Referenz-)Typ anstelle von T
gesetzt werden. Dazu typisieren wir:
MyGenericClass<String> testMitString = new MyGenericClass<>("Hallo");
System.out.println(testMitString.getValue());
MyGenericClass<Integer> testMitInteger = new MyGenericClass<>(42);
System.out.println(testMitInteger.getValue());
Zur Typisierung kann jeder beliebige Referenztyp, also auch Konto
, Rectangle
, Person
, Student
usw. verwendet werden, nicht aber Wertetypen. Beachten Sie, dass wenn wir unsere Klasse mit String
typisieren, dann muss im Konstruktor auch ein String
übergeben werden und wenn Integer
dann dort auch ein Integer
. Andernfalls lässt sich das Programm nicht übersetzen. Eine Klasse (bzw. ein Interface) kann mit beliebig vielen generischen Typen parametrisiert werden.
Welche Bezeichnungen Sie für die generischen Typen verwenden, bleibt Ihnen überlassen. Könnte z.B. auch Hallo
sein. es gibt aber Konventionen, an die Sie sich ruhig halten sollten:
Platzhalter | Bedeutung |
---|---|
E |
Element |
K |
Key |
N |
Number |
T |
Type |
V |
Value |
S , U |
2. und 3. Typ |
Manchmal werden an die generischen Typen spezielle Anforderungen gestellt. Angenommen, wir wollen die Klasse MyGenericClass<T>
um folgende Methode erweitern:
```java linenums="18"
public boolean isBigger(MyGenericClass<T> other)
{
return this.value.compareTo(other.value) > 0;
}
```
Dann bräuchten wir die Zusicherung, dass der Typ T
auch Comparable
implementiert hat, denn sonst könnten wir compareTo()
gar nicht aufrufen. Eine solche Zusicherung lässt sich mittels <T extends Comparable<T>>
beschreiben:
Beispiel einfaches Interface¶
Angenommen, wir haben folgendes einfaches funktionales Interface:
Dann könnten wir uns beliebige Methoden schreiben, die ein Addable
erwarten, z.B.
public static String concat(Addable<String, String> addable, String first, String second)
{
return addable.add(first, second);
}
public static Integer add(Addable<Integer, Integer> addable, Integer first, Integer second)
{
return addable.add(first, second);
}
public static List<Integer> insert(Addable<Integer, List<Integer>> addable, Integer first, Integer second)
{
return addable.add(first, second);
}
und bei Aufruf der Methoden jeweils mithilfe von Lambdas die Implementierung von R add(T t1, T t2)
angeben:
System.out.println(concat( (s1, s2) -> s1 + s2, "Hallo ", "FIW!"));
System.out.println(add( (s1, s2) -> s1 + s2, 3, 4));
System.out.println(insert( (s1, s2) -> List.of(s1, s2), 3, 4));
Wie könnte das Zusammenfügen zweier Sets aussehen?
public static Set<String> addAll(Addable<Set<String>, Set<String>> addable, Set<String> first, Set<String> second)
{
return addable.add(first, second);
}
System.out.println(addAll( (s1, s2) -> {
Set<String> all = new HashSet<>(s1);
all.addAll(s2);
return all;
}, new HashSet<>(List.of("A", "B", "C", "D")), new HashSet<>(List.of("C", "D", "E", "F"))));