Menu
Coddy logo textTech

Wildcards (?, extends, super)

Fait partie de la section Programmation Orientée Objet du Journey Java de Coddy — leçon 57 sur 87.

Les paramètres de type bornés restreignent les génériques à des types spécifiques, mais qu'en est-il lorsque vous travaillez avec des types génériques existants et que vous ne connaissez pas — ou ne vous souciez pas de — l'exact paramètre de type ? C'est là qu'interviennent les wildcards.

Le wildcard non borné ? représente un type inconnu. Il est utile lorsque vous souhaitez accepter n'importe quel type paramétré :

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

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

Le wildcard avec borne supérieure ? extends Type accepte le type spécifié ou n'importe laquelle de ses sous-classes. Utilisez-le lorsque vous avez besoin de lire à partir d'une structure générique :

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 d'Integer
sumNumbers(Arrays.asList(1.5, 2.5));       // Liste de Double

Le wildcard avec borne inférieure ? super Type accepte le type spécifié ou n'importe laquelle de ses superclasses. Utilisez-le lorsque vous avez besoin d'écrire dans une structure générique :

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

List<Number> numbers = new ArrayList<>();
addIntegers(numbers);  // Fonctionne - Number est un super-type d'Integer

Une règle utile : utilisez extends quand vous lisez uniquement (producteur), utilisez super quand vous écrivez uniquement (consommateur). C'est ce qu'on appelle le principe PECS — Producer Extends, Consumer Super.

challenge icon

Défi

Facile

Construisons un système de nourrissage de zoo qui démontre les trois types de jokers (wildcards) ! Vous allez créer une hiérarchie d'animaux et de nourriture, puis écrire des méthodes utilitaires qui utilisent des jokers non bornés, bornés supérieurement et bornés inférieurement pour gérer différents scénarios de nourrissage.

Vous organiserez votre code sur quatre fichiers :

  • Food.java : Créez une hiérarchie de classes simple pour la nourriture. Commencez par une classe de base Food qui possède un champ privé name (String), un constructeur pour l'initialiser et une méthode getName(). Créez ensuite deux sous-classes dans le même fichier : Meat qui étend Food (le constructeur appelle super(name)) et Vegetable qui étend Food (même modèle).
  • Animal.java : Créez une classe de base Animal avec un champ privé species (String), un constructeur et une méthode getSpecies(). Créez deux sous-classes : Carnivore et Herbivore, chacune étendant Animal et appelant le constructeur parent.
  • ZooFeeder.java : C'est ici que les jokers brillent ! Créez une classe utilitaire avec trois méthodes statiques :

    printInventory(List<?> items) - Utilise un joker non borné pour imprimer n'importe quelle liste. Parcourez la liste et imprimez chaque élément en utilisant toString(), un par ligne.

    calculateTotalFood(List<? extends Food> foods) - Utilise un joker borné supérieurement pour lire à partir d'une liste de n'importe quel type de Food. Retourne le nombre d'éléments sous forme d'int. Cela démontre la lecture à partir d'un producteur.

    addMeatToStock(List<? super Meat> stock, String meatName) - Utilise un joker borné inférieurement pour écrire dans une liste pouvant contenir Meat. Créez un nouvel objet Meat avec le nom donné et ajoutez-le au stock. Cela démontre l'écriture dans un consommateur.

  • Main.java : Rassemblez votre système de nourrissage de zoo ! Vous recevrez deux entrées : un nom de viande (String) et un nom de légume (String).

    Tout d'abord, créez une ArrayList<Food> et ajoutez un objet Meat avec le nom de la viande et un objet Vegetable avec le nom du légume. Imprimez "Food inventory:" puis appelez printInventory avec cette liste.

    Ensuite, imprimez une ligne vide, puis imprimez "Total food items: " suivi du résultat de calculateTotalFood sur votre liste de nourriture.

    Enfin, créez une nouvelle ArrayList<Food> appelée meatStock. Imprimez une ligne vide, puis "Adding to meat stock...". Appelez addMeatToStock avec cette liste et la chaîne "Beef". Imprimez ensuite "Meat stock after adding:" et appelez printInventory sur le stock de viande.

Vous recevrez deux entrées dans l'ordre : un nom de viande et un nom de légume.

Surchargez toString() dans votre classe Food pour retourner le nom de la nourriture, afin que l'affichage fonctionne correctement. Remarquez comment chaque type de joker sert un objectif différent : ? pour une flexibilité maximale lorsque vous avez seulement besoin de lire en tant qu'Object, ? extends lors de la lecture de types spécifiques, et ? super lors de l'écriture dans une collection !

Aide-mémoire

Java propose trois types de wildcards (caractères génériques) pour travailler avec des types génériques lorsque le paramètre de type exact est inconnu ou non pertinent.

Wildcard non borné (?)

Représente un type inconnu et accepte n'importe quel type paramétré :

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

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

Wildcard avec borne supérieure (? extends Type)

Accepte le type spécifié ou l'une de ses sous-classes. Utilisez-le lorsque vous avez besoin de lire à partir d'une structure générique :

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

Wildcard avec borne inférieure (? super Type)

Accepte le type spécifié ou l'une de ses super-classes. Utilisez-le lorsque vous avez besoin d'écrire dans une structure générique :

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

Principe PECS

Producer Extends, Consumer Super : Utilisez extends lorsque vous effectuez uniquement de la lecture (producteur), utilisez super lorsque vous effectuez uniquement de l'écriture (consommateur).

Essayez vous-même

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: Créer une ArrayList<Food> et ajouter des objets Meat et Vegetable
        
        // TODO: Afficher "Food inventory:" puis appeler printInventory
        
        // TODO: Afficher une ligne vide, puis "Total food items: [count]" en utilisant calculateTotalFood
        
        // TODO: Créer une nouvelle ArrayList<Food> nommée meatStock
        
        // TODO: Afficher une ligne vide, puis "Adding to meat stock..."
        
        // TODO: Appeler addMeatToStock avec meatStock et "Beef"
        
        // TODO: Afficher "Meat stock after adding:" puis appeler printInventory sur meatStock
    }
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet