Il s'agit d'un exemple concret d'une API de bibliothèque tierce, mais simplifié.
Compilé avec Oracle JDK 8u72
Considérez ces deux méthodes:
<X extends CharSequence> X getCharSequence() {
return (X) "hello";
}
<X extends String> X getString() {
return (X) "hello";
}
Les deux rapportent un avertissement "casting non vérifié" - je comprends pourquoi. La chose qui me déroute est pourquoi puis-je appeler
Integer x = getCharSequence();
et il compile? Le compilateur doit savoir que cela Integer
n'implémente pas 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-il expliquer pourquoi ce comportement serait-il considéré comme valide? En quoi cela serait-il utile?
Le client ne sait pas que cet appel est dangereux - le code du client se compile sans avertissement. Pourquoi la compilation ne l'avertirait-elle pas / n'émettrait-elle pas une erreur?
En outre, en quoi 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
Essayer de réussir 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 cela est signalé comme une erreur, pourquoi Integer x = getCharSequence();
pas?
Integer x = getCharSequence();
sera compilé, mais le casting sur le RHSInteger x = (Integer) getCharSequence();
échoue à la compilationRéponses:
CharSequence
est uninterface
. Par conséquent, même siSomeClass
ne met pas en œuvreCharSequence
il serait parfaitement possible de créer une classePar conséquent, vous pouvez écrire
car le type inféré
X
est le type d'intersectionSomeClass & CharSequence
.C'est un peu étrange dans le cas de
Integer
parce queInteger
c'est définitif, maisfinal
ne joue aucun rôle dans ces règles. Par exemple, vous pouvez écrireD'un autre côté, ce
String
n'est pas uninterface
, il serait donc impossible d'étendreSomeClass
pour obtenir un sous-type deString
, car java ne prend pas en charge l'héritage multiple pour les classes.Avec l'
List
exemple, vous devez vous rappeler que les génériques ne sont ni covariants ni contravariants. Cela signifie que siX
est un sous-type deY
,List<X>
n'est ni un sous-type ni un supertype deList<Y>
. PuisqueInteger
ne met pas en œuvreCharSequence
, vous ne pouvez pas utiliserList<Integer>
dans votredoCharSequence
méthode.Vous pouvez cependant obtenir ceci pour compiler
Si vous avez une méthode qui renvoie un
List<T>
comme celui-ci:tu peux faire
Encore une fois, c'est parce que le type inféré est
Integer & CharSequence
et qu'il s'agit d'un sous-type deInteger
.Les types d'intersection se produisent implicitement lorsque vous spécifiez plusieurs limites (par exemple
<T extends SomeClass & CharSequence>
).Pour plus d'informations, voici la partie du JLS où il explique le fonctionnement des limites de type. Vous pouvez inclure plusieurs interfaces, par exemple
mais seule la première borne peut être une non-interface.
la source
&
dans la définition générique. +1<T extends String & List & Comparator>
est ok mais<T extends String & Integer>
ne l'est pas, car ceInteger
n'est pas une interface.Collections.emptyList()
ainsi queOptional.empty()
. Ceux-ci renvoient des implémentations d'une interface générique, mais ne stockent rien.final
de compilation le serafinal
au moment de l'exécution.getCharSequence()
promet de retourner tout ce dontX
l'appelant a besoin, ce qui inclut le retour d'un type étendantInteger
et implémentantCharSequence
si l'appelant en a besoin et sous cette promesse, il est correct d'autoriser l'attribution du résultat àInteger
. C'est la méthodegetCharSequence()
qui est cassée car elle ne tient pas sa promesse, mais ce n'est pas la faute du compilateur.Le type qui est déduit par votre compilateur avant l'affectation pour
X
estInteger & CharSequence
. Ce type semble étrange, car ilInteger
est définitif, mais c'est un type parfaitement valide en Java. Il est ensuite jetéInteger
, ce qui est parfaitement OK.Il y a exactement une valeur possible pour le
Integer & CharSequence
genre:null
. Avec l'implémentation suivante:La mission suivante fonctionnera:
En raison de cette valeur possible, il n'y a aucune raison pour que l'affectation soit erronée, 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é sur cet anti-pattern de conception d'API . Vous ne devriez (presque) jamais concevoir une méthode générique pour renvoyer des types arbitraires car vous ne pouvez (presque) jamais garantir que le type inféré sera livré. Une exception sont des méthodes comme
Collections.emptyList()
, dans le cas desquelles le vide de la liste (et l'effacement de type générique) est la raison pour laquelle toute inférence<T>
fonctionnera:la source