Java: ambiguïté de surcharge de la méthode générique
Considérons le code suivant:
public class Converter {
public <K> MyContainer<K> pack(K key, String[] values) {
return new MyContainer<>(key);
}
public MyContainer<IntWrapper> pack(int key, String[] values) {
return new MyContainer<>(new IntWrapper(key));
}
public static final class MyContainer<T> {
public MyContainer(T object) { }
}
public static final class IntWrapper {
public IntWrapper(int i) { }
}
public static void main(String[] args) {
Converter converter = new Converter();
MyContainer<IntWrapper> test = converter.pack(1, new String[]{"Test", "Test2"});
}
}
le code ci-dessus se compile sans problème. Cependant, si l'on change String[]
String...
pack
signatures et new String[]{"Test", "Test2"}
"Test", "Test2"
, le compilateur se plaint de l'appel à converter.pack
être ambigu.
Maintenant, je peux comprendre pourquoi il pourrait être considéré comme ambigu (comme int
peut être autoboxé dans un Integer
, correspondant ainsi aux conditions, ou à l'absence de celles-ci, de K
). Cependant, ce que je ne comprends pas est pourquoi l'ambiguïté n'est pas là si vous utilisez String[]
au lieu de String...
.
quelqu'un Peut-il m'expliquer ce comportement étrange?
4 réponses
votre 1 st cas est assez simple. La méthode ci-dessous:
public MyContainer<IntWrapper> pack(int key, Object[] values)
est une correspondance exacte pour les arguments - (1, String[])
. À partir de JLS Section 15.12.2:
la première phase (§15.12.2.2) effectue la résolution de surcharge sans permettre la conversion de boxe ou de déboxage
maintenant, il n'y a pas de boxe impliquée en passant ces paramètres à la 2e méthode. Object[]
est un super type de String[]
. Et en passant String[]
argument Object[]
parameter était une invocation valide même avant Java 5.
compilateur semble jouer trick dans votre 2ème cas:
dans votre 2ème cas, puisque vous avez utilisé var-args, la résolution de surcharge de la méthode se fera en utilisant à la fois var-args, et boxing ou Unbox, comme dans la 3ème phase expliquée dans cette section de JLS:
la troisième phase (§15.12.2.4) permet de combiner la surcharge avec la variable les méthodes de l'art, la boxe, et le jeu de la boxe.
notez que la 2ème phase n'est pas applicable ici, en raison de l'utilisation de var-args:
la deuxième phase (§15.12.2.3) effectue la résolution de la surcharge tout en permettant la boxe et le désembuage, mais exclut tout de même l'utilisation de l'invocation de la méthode de l'intensité variable.
Maintenant ce qui se passe ici est compilateur n'infère pas correctement l'argument de type* (En fait, il l'infère correctement car le paramètre type est utilisé comme paramètre formel, voir la mise à jour vers la fin de cette réponse). Donc, pour votre invocation de méthode:
MyContainer<IntWrapper> test = converter.pack(1, "Test", "Test2");
le compilateur aurait dû déduire le type de K
dans la méthode générique d'être IntWrapper
, de la LHS. Mais il semble que c'est l'inférence K
Integer
type, en raison de laquelle vos deux méthodes sont maintenant également applicables pour cet appel de méthode, comme les deux requires var-args
ou boxing
.
cependant, si le résultat de cette méthode n'est pas assigné à une référence quelconque, alors je peux comprendre que le compilateur ne peut pas inférer le type correct comme dans ce cas, où est est parfaitement acceptable pour donner une erreur d'ambiguïté:
converter.pack(1, "Test", "Test2");
peut-être, juste pour maintenir la cohérence, il est aussi marqué ambigu pour le premier cas. Mais, encore une fois, je ne suis pas vraiment sûr, car je n'ai pas trouvé de source crédible de JLS, ou autre référence officielle qui parle de cette question. Je vais continuer à chercher, et si j'arrive à en trouver un, je mettrai à jour la réponse.
Laissez-le truc le compilateur par type explicite d'informations:
si vous changez la méthode invocation pour donner des informations de type explicite:
MyContainer<IntWrapper> test = converter.<IntWrapper>pack(1, "Test", "Test2");
Maintenant, le type K
sera inféré comme IntWrapper
, mais depuis 1
n'est pas convertible IntWrapper
, cette méthode est écartée, et la 2e méthode sera invoquée et fonctionnera parfaitement fin.
Franchement, je ne sais vraiment pas ce qui se passe ici. Je m'attendrais à ce que le compilateur infère le paramètre type du contexte d'invocation de la méthode dans le premier cas aussi, car cela fonctionne pour le problème suivant:
public static <T> HashSet<T> create(int size) {
return new HashSet<T>(size);
}
// Type inferred as `Integer`, from LHS.
HashSet<Integer> hi = create(10);
mais, il ne le fait pas dans ce cas. Donc cela peut éventuellement être un bug.
*ou peut-être que je ne comprends pas exactement comment le compilateur infère les arguments de type, quand le type n'est pas passé comme argument. Donc, pour en savoir plus à ce sujet, j'ai essayé de passer par - JLS §15.12.2.7 et JLS §15.12.2.8, qui est à propos de la façon dont le compilateur infère l'argument de type, mais qui va complètement au-dessus de ma tête.
donc, pour l'instant vous devez vivre avec, et utiliser l'alternative (en fournissant un argument de type explicite).
il s'avère que le compilateur ne jouait aucun tour:
comme expliqué en fin de compte dans le commentaire de @zhong.j.yu., le compilateur n'applique la section 15.12.2.8 que pour l'inférence de type, lorsqu'il ne parvient pas à l'inférer conformément à la section 15.12.2.7. Mais ici, il peut inférer le type comme Integer
à partir de l'argument passé, comme clairement le paramètre type est un paramètre format dans la méthode.
Donc, oui compilateur correctement déduit le type Integer
, et d'où l'ambiguïté est valide. Et maintenant, je pense que cette réponse est complète.
Ici, vous allez, la différence entre les deux méthodes ci-dessous: Méthode 1:
public MyContainer<IntWrapper> pack(int key, Object[] values) {
return new MyContainer<>(new IntWrapper(""));
}
Méthode 2:
public MyContainer<IntWrapper> pack(int key, Object ... values) {
return new MyContainer<>(new IntWrapper(""));
}
la méthode 2 est aussi bonne que
public MyContainer<IntWrapper> pack(Object ... values) {
return new MyContainer<>(new IntWrapper(""));
}
C'est pourquoi vous obtenez une ambiguïté..
EDIT Oui, je veux dire qu'ils sont les mêmes pour les compiler. Le but de l'utilisation d'arguments variables est de permettre à un utilisateur de définir une méthode lorsqu'il n'est pas sûr de la le nombre d'arguments d'un type donné.
donc si vous utilisez un objet comme argument variable, vous dites simplement le compilateur que je ne sais pas combien d'objets je vais envoyer et d'un autre côté, vous dites,"je passe un nombre entier et inconnu d'objets". Pour le compilateur, l'entier est un objet.
si vous voulez vérifier la validité essayez de passer un entier comme premier argument et ensuite passer un argument variable de String. Vous verrez la différence.
Pour par exemple:
public class Converter {
public static void a(int x, String... y) {
}
public static void a(String... y) {
}
public static void main(String[] args) {
a(1, "2", "3");
}
}
en outre, s'il vous plaît ne pas utiliser les tableaux et la variable args de façon interchangeable, ils ont quelques buts différents tout à fait.
lorsque vous utilisez varargs la méthode ne s'attend pas à un tableau mais à des paramètres différents du même type, qui peuvent être consultés de manière indexée.
Dans ce cas
(1) m(K, String[])
(2) m(int, String[])
m(1, new String[]{..});
m(1) satisfait à l' 15.12.2.3. Phase 2: Identifier les méthodes D'appariement applicables par la Conversion Invocation Method
m(2) satisfait 15.12.2.2. Phase 1: Identifier les méthodes D'appariement applicables par sous-typage
le compilateur s'arrête à la Phase 1; Il trouve m(2) comme seule méthode applicable à cette phase, donc m(2) est choisi.
(3) m(K, String...)
(4) m(int, String...)
m(1, str1, str2);
les Deux m(3) et m (4) satisfont 15.12.2.4. Phase 3: Identifier Les Méthodes D'Analyse Des Variables Applicables . Ni est plus spécifique que l'autre, donc l'ambiguïté.
- applicable par sous-typage
- applicable par l'invocation de méthode de conversion
- vararg, applicable par sous-typage
- vararg, applicable par l'invocation de méthode de conversion
La spec a fusionné les groupes 3 et 4 et les a traités tous les deux en Phase 3. Par conséquent, l'incohérence.
Pourquoi ont-ils le faire? Maye, elles se fatigué de il.
tout d'Abord, ce n'est que quelques premiers indices... peut modifier pour plus d'.
le compilateur recherche et sélectionne toujours la méthode la plus spécifique disponible. Bien qu'un peu lourde à lire, tout est spécifié dans JLS 15.12.2.5. Ainsi, en appelant
convertisseur.pack (1," Test"," Test2")
ce n'est pas déterminable pour le compilateur si le 1
doit être dissoute à K
ou int
. En d'autres termes, K peut s'appliquer n'importe quel type, donc c'est au même niveau que int/Integer.
La différence réside dans le nombre et le type des arguments. Considérer que new String[]{"Test", "Test2"}
est un tableau, alors que "Test", "Test2"
sont deux arguments de type String!
convertisseur.pack(1); // ambiguës, erreur du compilateur
convertisseur.pack(1, null); // appelle la méthode 2, avertissement du compilateur
convertisseur.pack(1, new String[]{}); // appelle la méthode 2, avertissement du compilateur
convertisseur.pack (1, new Object ());// ambiguous, erreur de compilation
convertisseur.pack(1, new Object[]{});// appelle la méthode 2, aucun avertissement n'