Java Language Decidere tra `T`,`? super T`, e `? estende T`


Esempio

La sintassi per i caratteri jolly limitati generici Java, che rappresentano il tipo sconosciuto da ? è:

  • ? extends T rappresenta un carattere jolly con limite superiore. Il tipo sconosciuto rappresenta un tipo che deve essere un sottotipo di T o di tipo T stesso.

  • ? super T rappresenta un carattere jolly con limite inferiore. Il tipo sconosciuto rappresenta un tipo che deve essere un supertipo di T o di tipo T stesso.

Come regola generale, dovresti usare

  • ? extends T se hai solo bisogno dell'accesso "lettura" ("input")
  • ? super T se hai bisogno dell'accesso "write" ("output")
  • T se hai bisogno di entrambi ("modifica")

Usando extends o super è solitamente meglio perché rende il tuo codice più flessibile (come in: consentire l'uso di sottotipi e supertipi), come vedrai di seguito.

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) {}
}

Il compilatore ora sarà in grado di rilevare alcuni usi errati:

 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!
}

Scegli la T giusta ? super T o ? extends T è necessario per consentire l'uso con sottotipi. Il compilatore può quindi garantire la sicurezza del tipo; non dovrebbe essere necessario eseguire il cast (che non è sicuro per il tipo e potrebbe causare errori di programmazione) se vengono utilizzati correttamente.

Se non è facile da capire, ricorda la regola PECS :

Il roditore usa " E xtends" e C onsumer usa " S uper".

(Il produttore ha solo accesso in scrittura e Consumer ha solo accesso in lettura)