58 votes

Plusieurs jokers sur une des méthodes génériques rend compilateur Java (et moi!) très confus

Nous allons d'abord envisager un scénario simple (voir la source sur les ideone.com):

import java.util.*;

public class TwoListsOfUnknowns {
    static void doNothing(List<?> list1, List<?> list2) { }

    public static void main(String[] args) {
        List<String> list1 = null;
        List<Integer> list2 = null;
        doNothing(list1, list2); // compiles fine!
    }
}

Les deux caractères génériques ne sont pas liés, c'est pourquoi vous pouvez appeler doNothing avec un List<String> et List<Integer>. En d'autres termes, les deux ? peut se référer à différents types. D'où ce qui suit ne compile pas, ce qui est prévisible (également sur ideone.com):

import java.util.*;

public class TwoListsOfUnknowns2 {
    static void doSomethingIllegal(List<?> list1, List<?> list2) {
        list1.addAll(list2); // DOES NOT COMPILE!!!
            // The method addAll(Collection<? extends capture#1-of ?>)
            // in the type List<capture#1-of ?> is not applicable for
            // the arguments (List<capture#2-of ?>)
    }
}

C'est très bien, mais voici où les choses commencent à devenir très déroutant (comme on le voit sur ideone.com):

import java.util.*;

public class LOLUnknowns1 {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }
}

Le code ci-dessus compile pour moi dans Eclipse et sur sun-jdk-1.6.0.17 dans ideone.com mais faut-il? N'est-il pas possible que nous ayons un List<List<Integer>> lol et List<String> list, l'analogie de deux indépendants des caractères génériques des situations d' TwoListsOfUnknowns?

En fait la suivante légère modification vers cette direction ne compile pas, ce qui est prévisible (comme on le voit sur ideone.com):

import java.util.*;

public class LOLUnknowns2 {
    static void rightfullyIllegal(
            List<List<? extends Number>> lol, List<?> list) {

        lol.add(list); // DOES NOT COMPILE! As expected!!!
            // The method add(List<? extends Number>) in the type
            // List<List<? extends Number>> is not applicable for
            // the arguments (List<capture#1-of ?>)
    }
}

Donc il semble que le compilateur est en train de faire son travail, mais ensuite, on se présente (comme on le voit sur ideone.com):

import java.util.*;

public class LOLUnknowns3 {
    static void probablyIllegalAgain(
            List<List<? extends Number>> lol, List<? extends Number> list) {

        lol.add(list); // compiles fine!!! how come???
    }
}

Encore une fois, on peut avoir par exemple un List<List<Integer>> lol et List<Float> list,, donc il ne devrait pas compiler, droit?

En fait, revenons à la plus simple LOLUnknowns1 (deux illimitée des caractères génériques) et essayer de voir si on peut en effet invoquer probablyIllegal , en quelque sorte. Nous allons essayer de la "facile" d'abord et choisir le même type pour les deux caractères génériques (comme on le voit sur ideone.com):

import java.util.*;

public class LOLUnknowns1a {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }

    public static void main(String[] args) {
        List<List<String>> lol = null;
        List<String> list = null;
        probablyIllegal(lol, list); // DOES NOT COMPILE!!
            // The method probablyIllegal(List<List<?>>, List<?>)
            // in the type LOLUnknowns1a is not applicable for the
            // arguments (List<List<String>>, List<String>)
    }
}

Cela n'a aucun sens! Ici, nous n'avons même pas essayé d'utiliser deux types différents, et cela ne compile pas! En faisant un List<List<Integer>> lol et List<String> list donne également une semblable erreur de compilation! En fait, de mon expérimentation, la seule façon que le code compile est si le premier argument est explicite null type (comme on le voit sur ideone.com):

import java.util.*;

public class LOLUnknowns1b {
    static void probablyIllegal(List<List<?>> lol, List<?> list) {
        lol.add(list); // this compiles!! how come???
    }

    public static void main(String[] args) {
        List<String> list = null;
        probablyIllegal(null, list); // compiles fine!
            // throws NullPointerException at run-time
    }
}

Donc les questions sont, en ce qui concerne LOLUnknowns1, LOLUnknowns1a et LOLUnknowns1b:

  • Quels sont les types d'arguments n' probablyIllegal accepter?
  • Devrait - lol.add(list); de la compilation? Est-il typesafe?
  • Est-ce un bug du compilateur ou suis-je incompréhension de la capture des règles de conversion pour les jokers?

Annexe A: Double LOL?

Dans le cas où quelqu'un est curieux, cette compile très bien (comme on le voit sur ideone.com):

import java.util.*;

public class DoubleLOL {
    static void omg2xLOL(List<List<?>> lol1, List<List<?>> lol2) {
        // compiles just fine!!!
        lol1.addAll(lol2);
        lol2.addAll(lol1);
    }
}

Annexe B: Imbriquée des caractères génériques -- ce que signifient-ils???

Une enquête plus approfondie indique que peut-être plusieurs caractères génériques n'a rien à voir avec le problème, mais plutôt un imbriquée générique est la source de la confusion.

import java.util.*;

public class IntoTheWild {

    public static void main(String[] args) {
        List<?> list = new ArrayList<String>(); // compiles fine!

        List<List<?>> lol = new ArrayList<List<String>>(); // DOES NOT COMPILE!!!
            // Type mismatch: cannot convert from
            // ArrayList<List<String>> to List<List<?>>
    }
}

Ainsi, il semble peut-être un List<List<String>> n'est pas un List<List<?>>. En effet, alors que tout List<E> est List<?>, il ne ressemble pas du tout List<List<E>> est List<List<?>> (comme on le voit sur ideone.com):

import java.util.*;

public class IntoTheWild2 {
    static <E> List<?> makeItWild(List<E> list) {
        return list; // compiles fine!
    }
    static <E> List<List<?>> makeItWildLOL(List<List<E>> lol) {
        return lol;  // DOES NOT COMPILE!!!
            // Type mismatch: cannot convert from
            // List<List<E>> to List<List<?>>
    }
}

Une nouvelle question se pose alors: quel est un List<List<?>>?

70voto

polygenelubricants Points 136838

L'Annexe B indique, cela n'a rien à voir avec de multiples caractères génériques, mais plutôt de l'incompréhension qu' List<List<?>> signifie vraiment.

Nous allons tout d'abord rappeler ce que cela signifie que Java génériques est invariant:

  1. Un Integer est Number
  2. Un List<Integer> est PAS un List<Number>
  3. Un List<Integer> EST un List<? extends Number>

Nous avons maintenant tout simplement appliquer le même argument à notre liste imbriquée situation (voir l'annexe pour plus de détails):

  1. Un List<String> est (captureable par) List<?>
  2. Un List<List<String>> est PAS (captureable par) List<List<?>>
  3. Un List<List<String>> EST (captureable par) List<? extends List<?>>

Avec cette compréhension, tous les extraits de la question peut être expliqué. La confusion vient en (faussement) à croire qu'un type comme List<List<?>> pouvez capturer des types comme List<List<String>>, List<List<Integer>>, etc. Ce n'est PAS vrai.

C'est, List<List<?>>:

  • est PAS une liste dont les éléments sont des listes de certains un type inconnu.
    • ... ce serait un List<? extends List<?>>
  • Au lieu de cela, c'est une liste dont les éléments sont des listes de TOUT type.

Extraits de

Voici un extrait de code pour illustrer les points ci-dessus:

List<List<?>> lolAny = new ArrayList<List<?>>();

lolAny.add(new ArrayList<Integer>());
lolAny.add(new ArrayList<String>());

// lolAny = new ArrayList<List<String>>(); // DOES NOT COMPILE!!

List<? extends List<?>> lolSome;

lolSome = new ArrayList<List<String>>();
lolSome = new ArrayList<List<Integer>>();

Plus d'extraits

Voici encore un autre exemple avec délimitée imbriquée générique:

List<List<? extends Number>> lolAnyNum = new ArrayList<List<? extends Number>>();

lolAnyNum.add(new ArrayList<Integer>());
lolAnyNum.add(new ArrayList<Float>());
// lolAnyNum.add(new ArrayList<String>());     // DOES NOT COMPILE!!

// lolAnyNum = new ArrayList<List<Integer>>(); // DOES NOT COMPILE!!

List<? extends List<? extends Number>> lolSomeNum;

lolSomeNum = new ArrayList<List<Integer>>();
lolSomeNum = new ArrayList<List<Float>>();
// lolSomeNum = new ArrayList<List<String>>(); // DOES NOT COMPILE!!

Retour à la question

Pour revenir à la des extraits dans la question, la suivante se comporte comme prévu (comme on le voit sur ideone.com):

public class LOLUnknowns1d {
    static void nowDefinitelyIllegal(List<? extends List<?>> lol, List<?> list) {
        lol.add(list); // DOES NOT COMPILE!!!
            // The method add(capture#1-of ? extends List<?>) in the
            // type List<capture#1-of ? extends List<?>> is not 
            // applicable for the arguments (List<capture#3-of ?>)
    }
    public static void main(String[] args) {
        List<Object> list = null;
        List<List<String>> lolString = null;
        List<List<Integer>> lolInteger = null;

        // these casts are valid
        nowDefinitelyIllegal(lolString, list);
        nowDefinitelyIllegal(lolInteger, list);
    }
}

lol.add(list); est illégale, car on peut avoir un List<List<String>> lol et List<Object> list. En fait, si nous en commentaire, la fausse déclaration, le code compile et c'est exactement ce que nous avons avec le premier appel en main.

Tous les de la probablyIllegal méthodes dans la question, ne sont pas illégaux. Ils sont tout à fait légal et typesafe. Il n'y a absolument pas de bug dans le compilateur. Il est en train de faire exactement ce qu'il est censé faire.


Références

Questions connexes


Annexe: Les règles de capture et de conversion

(Cette question a été soulevée dans la première révision de la réponse; c'est un digne complément au type d'invariant argument.)

5.1.10 Capture De Conversion

Laissez - G nom d'un type générique déclaration n formelle paramètres de type Un1...n correspondant aux limites U1...Un. Il existe une capture de conversion de G<T1...Tn> pour G<S1...Sn>, où, pour 1 <= i <= n:

  1. Si Tje est un générique de type argument de la forme ? alors ...
  2. Si Tje est un générique de type argument de la forme ? extends B- je, alors ...
  3. Si Tje est un générique de type argument de la forme ? super B- je, alors ...
  4. Sinon, Si = Tje.

La Capture de la conversion n'est pas appliquée de manière récursive.

Cette section peut être source de confusion, notamment en ce qui concerne la non-application récursive de la capture de conversion (ci-après CC), mais l'essentiel est que pas tous ? peuvent CC; elle dépend de l'endroit où il apparaît. Il n'y a pas d'application récursive dans la règle 4, mais lorsque les règles 2 ou 3 s'applique, puis le Bje peut lui-même être le résultat d'un CC.

Nous allons travailler à travers quelques exemples simples:

  • List<?> peuvent CC List<String>
    • L' ? peuvent CC par la règle 1
  • List<? extends Number> peuvent CC List<Integer>
    • L' ? peuvent CC par la règle 2
    • En application de l'article 2, Bi est tout simplement Number
  • List<? extends Number> peut PAS de CC List<String>
    • L' ? peuvent CC par la règle 2, mais erreur de compilation se produit en raison d'une incompatibilité de types

Maintenant, nous allons essayer certains de nidification:

  • List<List<?>> peut PAS de CC List<List<String>>
    • L'article 4 s'applique, et CC n'est pas récursive, donc l' ? peut PAS de CC
  • List<? extends List<?>> peuvent CC List<List<String>>
    • La première ? peuvent CC par la règle 2
    • En application de l'article 2, Bi est maintenant un List<?>, ce qui peut CC List<String>
    • Les deux ? peuvent CC
  • List<? extends List<? extends Number>> peuvent CC List<List<Integer>>
    • La première ? peuvent CC par la règle 2
    • En application de l'article 2, Bi est maintenant un List<? extends Number>, ce qui peut CC List<Integer>
    • Les deux ? peuvent CC
  • List<? extends List<? extends Number>> peut PAS de CC List<List<Integer>>
    • La première ? peuvent CC par la règle 2
    • En application de l'article 2, Bi est maintenant un List<? extends Number>, ce qui peut CC, mais donne une erreur de compilation lorsqu'il est appliqué à l' List<Integer>
    • Les deux ? peuvent CC

Pour illustrer pourquoi certains ? peuvent CC et les autres ne peuvent pas, considérons la règle suivante: vous pouvez ne PAS instancier directement un type générique. Qui est, de ce qui suit donne une erreur de compilation:

    // WildSnippet1
    new HashMap<?,?>();         // DOES NOT COMPILE!!!
    new HashMap<List<?>, ?>();  // DOES NOT COMPILE!!!
    new HashMap<?, Set<?>>();   // DOES NOT COMPILE!!!

Cependant, la suivante compile parfaitement:

    // WildSnippet2
    new HashMap<List<?>,Set<?>>();            // compiles fine!
    new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!

La raison en WildSnippet2 compile est parce que, comme expliqué ci-dessus, aucun des ? peuvent CC. En WildSnippet1, soit l' K ou V (ou les deux) de l' HashMap<K,V> peuvent CC, ce qui rend l'instanciation directe par le biais new illégal.

2voto

Colin Hebert Points 40084
  • Aucun argument avec les génériques devrait être acceptée. Dans le cas d' LOLUnknowns1b le null est acceptée que si le premier argument est typé en tant que List. Par exemple, cela ne compile :

    List lol = null;
    List<String> list = null;
    probablyIllegal(lol, list);
    
  • À mon humble avis, lol.add(list); ne devrait même pas compiler mais comme lol.add() besoin d'un argument de type List<?> et que la liste s'adapte en List<?> il fonctionne.
    Un étrange exemple qui me font penser à cette théorie est la suivante :

    static void probablyIllegalAgain(List<List<? extends Number>> lol, List<? extends Integer> list) {
        lol.add(list); // compiles fine!!! how come???
    }
    

    lol.add() besoin d'un argument de type List<? extends Number> et la liste est typé en tant que List<? extends Integer>, elle se situe. Il ne fonctionnera pas si elle ne correspond pas. Même chose pour le double LOL, et d'autres imbriqués les caractères génériques, aussi longtemps que la première capture correspond à la seconde, tout est ok (et souldn pas être).

  • Encore une fois, je ne suis pas sûr, mais il ne semble vraiment comme un bug.

  • Je suis content de pas être le seul à utiliser lol variables de tous les temps.

Ressources :
http://www.angelikalanger.comune FAQ sur les génériques

Modifications :

  1. Ajouté commentaire à propos de la Double Lol
  2. Et imbriqués les caractères génériques.

0voto

irreputable Points 25577

n'étant pas un expert, mais je pense que je peux le comprendre.

nous allons changer votre exemple de quelque chose d'équivalent, mais avec plus de distinguer les types:

static void probablyIllegal(List<Class<?>> x, Class<?> y) {
    x.add(y); // this compiles!! how come???
}

nous allons modifier la Liste de [] pour être plus éclairante:

static void probablyIllegal(Class<?>[] x, Class<?> y) {
    x.add(y); // this compiles!! how come???
}

maintenant, x n'est pas un tableau de certains type de classe. c'est un ensemble de tout type de classe. il peut contenir un Class<String> et un Class<Int>. cela ne peut pas être exprimé avec des paramètre de type:

static<T> void probablyIllegal(Class<T>[] x  //homogeneous! not the same!

Class<?> est un super type d' Class<T> pour toute T. Si l'on pense qu'un type est un ensemble d'objets, définissez Class<?> est l'union de tous les ensembles de Class<T> tous T. (n'inclut itselft? Je ne sais pas...)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X