Java Language Décider entre `T`,`? super T`, et `? étend T`


Exemple

La syntaxe des génériques Java limite les caractères génériques, représentant le type inconnu par ? est:

  • ? extends T représente un caractère générique supérieur. Le type inconnu représente un type qui doit être un sous-type de T ou un type T lui-même.

  • ? super T représente un caractère générique limité inférieur. Le type inconnu représente un type qui doit être un supertype de T ou un type T lui-même.

En règle générale, vous devez utiliser

  • ? extends T si vous avez seulement besoin d'un accès "read" ("input")
  • ? super T si vous avez besoin d'un accès "écriture" ("sortie")
  • T si vous avez besoin des deux ("modifier")

Utiliser extends ou super est généralement préférable car cela rend votre code plus flexible (comme dans: autoriser l'utilisation de sous-types et de supertypes), comme vous le verrez ci-dessous.

class Shoe {}
class IPhone {}
interface Fruit {}
class Apple implements Fruit {}
class Banana implements Fruit {}
class GrannySmith extends Apple {}

   public class FruitHelper {

        public void eatAll(Collection<? extends Fruit> fruits) {}

        public void addApple(Collection<? super Apple> apples) {}
}

Le compilateur sera maintenant capable de détecter certaines mauvaises utilisations:

 public class GenericsTest {
      public static void main(String[] args){
  FruitHelper fruitHelper = new FruitHelper() ;
    List<Fruit> fruits = new ArrayList<Fruit>();
    fruits.add(new Apple()); // Allowed, as Apple is a Fruit
    fruits.add(new Banana()); // Allowed, as Banana is a Fruit
    fruitHelper.addApple(fruits); // Allowed, as "Fruit super Apple"
    fruitHelper.eatAll(fruits); // Allowed

    Collection<Banana> bananas = new ArrayList<>();
    bananas.add(new Banana()); // Allowed
    //fruitHelper.addApple(bananas); // Compile error: may only contain Bananas!
    fruitHelper.eatAll(bananas); // Allowed, as all Bananas are Fruits

    Collection<Apple> apples = new ArrayList<>();
    fruitHelper.addApple(apples); // Allowed
    apples.add(new GrannySmith()); // Allowed, as this is an Apple
    fruitHelper.eatAll(apples); // Allowed, as all Apples are Fruits.
    
    Collection<GrannySmith> grannySmithApples = new ArrayList<>();
    fruitHelper.addApple(grannySmithApples); //Compile error: Not allowed.
                                   // GrannySmith is not a supertype of Apple
    apples.add(new GrannySmith()); //Still allowed, GrannySmith is an Apple
    fruitHelper.eatAll(grannySmithApples);//Still allowed, GrannySmith is a Fruit

    Collection<Object> objects = new ArrayList<>();
    fruitHelper.addApple(objects); // Allowed, as Object super Apple
    objects.add(new Shoe()); // Not a fruit
    objects.add(new IPhone()); // Not a fruit
    //fruitHelper.eatAll(objects); // Compile error: may contain a Shoe, too!
}

Choisir le bon T , ? super T ou ? extends T est nécessaire pour permettre l'utilisation avec des sous-types. Le compilateur peut alors assurer la sécurité du type; vous ne devriez pas avoir besoin de lancer (ce qui n'est pas sûr et peut causer des erreurs de programmation) si vous les utilisez correctement.

Si ce n'est pas facile à comprendre, n'oubliez pas la règle PECS :

P roducer utilise des "E" et C xtends utilise onsumer "S uper".

(Le producteur n'a qu'un accès en écriture et le consommateur n'a qu'un accès en lecture)