Zum Inhalt

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:

public class MyGenericClass<T>
{
    T value;

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

    public T getValue()
    {
        return value;
    }
}

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:

public class MyGenericClass<T extends Comparable<T>>
{
    T value;

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

    public T getValue()
    {
        return value;
    }

    public boolean isBigger(MyGenericClass<T> other)
    {
        return this.value.compareTo(other.value) > 0;
    }
}

Beispiel einfaches Interface

Angenommen, wir haben folgendes einfaches funktionales Interface:

@FunctionalInterface
public interface Addable<T, R>
{
    public R add(T t1, T t2);
}

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"))));