171 votes

Limite supérieure du type de retour générique - interface et classe - code étonnamment valide

C'est un exemple réel d'un 3ème partie de la bibliothèque de l'API, mais simplifiée.

Compilé avec Oracle JDK 8u72

Tenir compte de ces deux méthodes:

<X extends CharSequence> X getCharSequence() {
    return (X) "hello";
}

<X extends String> X getString() {
    return (X) "hello";
}

Les deux état d'un "décochée cast" avertissement - je comprends pourquoi. La chose qui me déroute est pourquoi je l'appelle

Integer x = getCharSequence();

et il compile? Le compilateur doit savoir qu' Integer ne met pas en oeuvre CharSequence. L'appel à

Integer y = getString();

donne une erreur (comme prévu)

incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String

Quelqu'un peut m'expliquer pourquoi ce comportement sera considéré comme valide? Comment serait-il utile?

Le client ne sait pas que c'est dangereux - le client du code compile sans warning. Pourquoi ne pas les compiler de les avertir que le problème / une erreur?

Aussi, comment est-il différent de cet exemple:

<X extends CharSequence> void doCharSequence(List<X> l) {
}

List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles

List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error

En essayant de passer List<Integer> donne une erreur, comme prévu:

method doCharSequence in class generic.GenericTest cannot be applied to given types;
  required: java.util.List<X>
  found: java.util.List<java.lang.Integer>
  reason: inference variable X has incompatible bounds
    equality constraints: java.lang.Integer
    upper bounds: java.lang.CharSequence

Si c'est signalé comme une erreur, pourquoi Integer x = getCharSequence(); n'est-ce pas?

184voto

Paul Boddington Points 9671

CharSequence est interface. Par conséquent, même si SomeClass ne met pas en oeuvre CharSequence , il serait parfaitement possible de créer une classe

class SubClass extends SomeClass implements CharSequence

Par conséquent, vous pouvez écrire

SomeClass c = getCharSequence();

parce que le type inféré X est l'intersection de type SomeClass & CharSequence.

C'est un peu bizarre dans le cas de l' Integer car Integer est définitif, mais final ne jouent aucun rôle dans ces règles. Par exemple, vous pouvez écrire

<T extends Integer & CharSequence>

D'autre part, String n'est pas un interface, de sorte qu'il serait impossible d'étendre SomeClass pour obtenir un sous-type d' String, parce que java ne supporte pas le multi-héritage de classes.

Avec l' List exemple, vous devez vous rappeler que les génériques ne sont ni des covariants ni contravariant. Cela signifie que si X est un sous-type d' Y, List<X> n'est ni un sous-type, ni un supertype de List<Y>. Depuis Integer ne met pas en oeuvre CharSequence, vous ne pouvez pas utiliser List<Integer> votre doCharSequence méthode.

Vous pouvez, toutefois, obtenir ce à compiler

<T extends Integer & CharSequence> void foo(List<T> list) {
    doCharSequence(list);
}  

Si vous avez une méthode qui retourne un List<T> comme ceci:

static <T extends CharSequence> List<T> foo() 

vous pouvez le faire

List<? extends Integer> list = foo();

Encore une fois, c'est parce que le type inféré est - Integer & CharSequence et c'est un sous-type d' Integer.

Intersection types implicitement lorsque vous spécifiez plusieurs limites (par exemple, <T extends SomeClass & CharSequence>).

Pour de plus amples informations, ici est la partie de la JLS où il explique comment le type de limites de travail. Vous pouvez inclure plusieurs interfaces, par exemple

<T extends String & CharSequence & List & Comparator>

mais seul le premier lié peut être un non-interface.

59voto

Lukas Eder Points 48046

Le type qui est déduit par votre compilateur avant la cession X est Integer & CharSequence. Ce type se sent bizarre, car Integer est définitif, mais c'est un bon type en Java. Il est alors jeté à l' Integer, ce qui est parfaitement OK.

Il y a exactement une seule valeur possible pour l' Integer & CharSequence type: null. Avec la mise en œuvre suivants:

<X extends CharSequence> X getCharSequence() {
    return null;
}

L'affectation suivante:

Integer x = getCharSequence();

En raison de cette valeur possible, il n'y a aucune raison pour laquelle l'assignation doit être mal, même si elle est évidemment inutile. Un avertissement serait utile.

Le vrai problème est l'API, pas le site d'appel

En fait, j'ai récemment blogué à propos de cette conception d'API anti-modèle. Vous devriez (presque) jamais de la conception d'une méthode générique pour revenir types arbitraires parce que vous pouvez (presque) jamais la garantie que le type inféré seront livrés. Une exception ne sont des méthodes comme Collections.emptyList(), dans le cas dont le vide de la liste (et de type générique effacement) est la raison pour laquelle aucune inférence pour <T> :

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}

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