Menu
Coddy logo textTech

Wildcards (?, extends, super)

Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy — Lektion 57 von 87.

Eingeschränkte Typparameter beschränken Generics auf bestimmte Typen, aber was ist, wenn Sie mit vorhandenen generischen Typen arbeiten und den genauen Typparameter nicht kennen – oder er Ihnen egal ist? Hier kommen Wildcards ins Spiel.

Die unbeschränkte Wildcard ? repräsentiert einen unbekannten Typ. Sie ist nützlich, wenn Sie jeden beliebigen parametrisierten Typ akzeptieren möchten:

public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

printList(new ArrayList<String>());   // Funktioniert
printList(new ArrayList<Integer>());  // Funktioniert

Die nach oben beschränkte Wildcard (upper bounded wildcard) ? extends Type akzeptiert den angegebenen Typ oder eine seiner Unterklassen. Verwenden Sie sie, wenn Sie aus einer generischen Struktur lesen müssen:

public static double sumNumbers(List<? extends Number> numbers) {
    double sum = 0;
    for (Number n : numbers) {
        sum += n.doubleValue();
    }
    return sum;
}

sumNumbers(Arrays.asList(1, 2, 3));        // Liste von Integer
sumNumbers(Arrays.asList(1.5, 2.5));       // Liste von Double

Die nach unten beschränkte Wildcard ? super Type akzeptiert den angegebenen Typ oder eine seiner Superklassen. Verwenden Sie sie, wenn Sie in eine generische Struktur schreiben müssen:

public static void addIntegers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

List<Number> numbers = new ArrayList<>();
addIntegers(numbers);  // Funktioniert - Number ist ein Supertyp von Integer

Eine hilfreiche Richtlinie: Verwenden Sie extends, wenn Sie nur lesen (Producer), verwenden Sie super, wenn Sie nur schreiben (Consumer). Dies ist als das PECS-Prinzip bekannt – Producer Extends, Consumer Super.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Zoo-Fütterungssystem bauen, das alle drei Arten von Wildcards demonstriert! Sie werden eine Hierarchie von Tieren und Futter erstellen und dann Hilfsmethoden schreiben, die ungebundene (unbounded), nach oben begrenzte (upper bounded) und nach unten begrenzte (lower bounded) Wildcards verwenden, um verschiedene Fütterungsszenarien zu handhaben.

Sie werden Ihren Code über vier Dateien organisieren:

  • Food.java: Erstellen Sie eine einfache Klassenhierarchie für Futter. Beginnen Sie mit einer Basisklasse Food, die ein privates Feld name (String), einen Konstruktor zu dessen Initialisierung und eine getName()-Methode besitzt. Erstellen Sie dann zwei Unterklassen in derselben Datei: Meat erweitert Food (der Konstruktor ruft super(name) auf) und Vegetable erweitert Food (gleiches Muster).
  • Animal.java: Erstellen Sie eine Basisklasse Animal mit einem privaten Feld species (String), einem Konstruktor und einer getSpecies()-Methode. Erstellen Sie zwei Unterklassen: Carnivore und Herbivore, die jeweils Animal erweitern und den Eltern-Konstruktor aufrufen.
  • ZooFeeder.java: Hier glänzen die Wildcards! Erstellen Sie eine Hilfsklasse mit drei statischen Methoden:

    printInventory(List<?> items) - Verwendet eine ungebundene Wildcard, um eine beliebige Liste auszugeben. Durchlaufen Sie die Liste in einer Schleife und geben Sie jedes Element mit toString() aus, eines pro Zeile.

    calculateTotalFood(List<? extends Food> foods) - Verwendet eine nach oben begrenzte Wildcard, um aus einer Liste eines beliebigen Food-Typs zu lesen. Geben Sie die Anzahl der Elemente als int zurück. Dies demonstriert das Lesen von einem Producer.

    addMeatToStock(List<? super Meat> stock, String meatName) - Verwendet eine nach unten begrenzte Wildcard, um in eine Liste zu schreiben, die Meat aufnehmen kann. Erstellen Sie ein neues Meat-Objekt mit dem angegebenen Namen und fügen Sie es dem Bestand hinzu. Dies demonstriert das Schreiben in einen Consumer.

  • Main.java: Bringen Sie Ihr Zoo-Fütterungssystem zusammen! Sie erhalten zwei Eingaben: einen Fleisch-Namen (String) und einen Gemüse-Namen (String).

    Erstellen Sie zuerst eine ArrayList<Food> und fügen Sie ein Meat-Objekt mit dem Fleisch-Namen und ein Vegetable-Objekt mit dem Gemüse-Namen hinzu. Geben Sie Food inventory: aus und rufen Sie dann printInventory mit dieser Liste auf.

    Geben Sie als Nächstes eine Leerzeile aus, und drucken Sie dann Total food items: [count] unter Verwendung von calculateTotalFood auf Ihrer Futterliste aus.

    Erstellen Sie schließlich eine neue ArrayList<Food> namens meatStock. Geben Sie eine Leerzeile aus, dann Adding to meat stock.... Rufen Sie addMeatToStock mit dieser Liste und dem String "Beef" auf. Geben Sie dann Meat stock after adding: aus und rufen Sie printInventory für den Fleischbestand auf.

Sie erhalten zwei Eingaben in der Reihenfolge: einen Fleisch-Namen und einen Gemüse-Namen.

Überschreiben Sie toString() in Ihrer Food-Klasse, um den Namen des Futters zurückzugeben, damit die Ausgabe korrekt funktioniert. Beachten Sie, wie jeder Wildcard-Typ einem anderen Zweck dient: ? für maximale Flexibilität, wenn Sie nur als Object lesen müssen, ? extends beim Lesen spezifischer Typen und ? super beim Schreiben in eine Collection!

Spickzettel

Java bietet drei Arten von Wildcards für die Arbeit mit generischen Typen an, wenn der genaue Typparameter unbekannt oder irrelevant ist.

Unbegrenzte Wildcard (?)

Repräsentiert einen unbekannten Typ und akzeptiert jeden parametrisierten Typ:

public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

printList(new ArrayList<String>());   // Works
printList(new ArrayList<Integer>());  // Works

Obere begrenzte Wildcard (? extends Type)

Akzeptiert den angegebenen Typ oder eine seiner Unterklassen. Verwenden Sie dies, wenn Sie aus einer generischen Struktur lesen müssen:

public static double sumNumbers(List<? extends Number> numbers) {
    double sum = 0;
    for (Number n : numbers) {
        sum += n.doubleValue();
    }
    return sum;
}

sumNumbers(Arrays.asList(1, 2, 3));        // List of Integer
sumNumbers(Arrays.asList(1.5, 2.5));       // List of Double

Untere begrenzte Wildcard (? super Type)

Akzeptiert den angegebenen Typ oder eine seiner Superklassen. Verwenden Sie dies, wenn Sie in eine generische Struktur schreiben müssen:

public static void addIntegers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

List<Number> numbers = new ArrayList<>();
addIntegers(numbers);  // Works - Number is a supertype of Integer

PECS-Prinzip

Producer Extends, Consumer Super: Verwenden Sie extends, wenn Sie nur lesen (Producer), verwenden Sie super, wenn Sie nur schreiben (Consumer).

Probier es selbst

import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String meatName = scanner.nextLine();
        String vegetableName = scanner.nextLine();
        
        // TODO: Erstelle eine ArrayList<Food> und füge Meat- und Vegetable-Objekte hinzu
        
        // TODO: Gib "Food inventory:" aus und rufe dann printInventory auf
        
        // TODO: Gib eine Leerzeile aus, dann "Total food items: [count]" unter Verwendung von calculateTotalFood
        
        // TODO: Erstelle eine neue ArrayList<Food> namens meatStock
        
        // TODO: Gib eine Leerzeile aus, dann "Adding to meat stock..."
        
        // TODO: Rufe addMeatToStock mit meatStock und "Beef" auf
        
        // TODO: Gib "Meat stock after adding:" aus und rufe dann printInventory für meatStock auf
    }
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung