34 votes

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 compile sans problèmes. Cependant, si l'on change String[] de String... dans les deux pack signatures et new String[]{"Test", "Test2"} de "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 autoboxed en Integer, ce qui correspond aux les conditions, ou l'absence, de K). Cependant, ce que je ne peux pas comprendre, c'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?

14voto

Rohit Jain Points 90368

Votre 1st 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[]). De JLS Section 15.12.2:

La première phase (§15.12.2.2) effectue la résolution de surcharge, sans permettre de boxe ou unboxing de conversion

Maintenant, il n'est pas de la boxe, en passant les paramètres pour la 2ème méthode. En tant que Object[] est un super type d' String[]. Et en passant String[] argument pour Object[] paramètre est valide invocation avant même de Java 5.


Compilateur semble jouer un tour dans votre 2e cas:

Dans le 2ème cas, puisque vous avez utilisé var-args, la surcharge de la méthode de résolution sera fait en utilisant les deux var-args, et de la boxe ou unboxing, comme pour la 3ème phase expliqué dans ce JLS section:

La troisième phase (§15.12.2.4) permet de surcharger être combinée avec une variable d'arité de méthodes, de boxe, et unboxing.

Remarque, la phase 2 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 surcharge, tout en permettant boxing et unboxing, mais encore s'oppose à l'utilisation de la variable d'arité de l'invocation de méthode.

Maintenant ce qui se passe ici est le compilateur n'est pas inférer le type d'argument correctement* (en Fait, c'est inférer correctement le type de paramètre 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");

compilateur doit avoir déduit le type d' K au générique de la méthode de IntWrapper, à partir de la LHS. Mais il semble que c'est l'inférence K un Integer type, en raison de laquelle les deux méthodes sont désormais également applicable pour l'appel de cette méthode, à la fois comme l'exige var-args ou boxing.

Toutefois, si le résultat de cette méthode n'est pas attribué à certains de référence, alors je peux comprendre que le compilateur ne peut pas en déduire un type approprié, car dans ce cas, où est-il est parfaitement acceptable de donner à une erreur d'ambiguïté:

converter.pack(1, "Test", "Test2");

Peut-être que je pense, pour maintenir la cohérence, c'est aussi marqué ambigu pour le premier cas. Mais, encore une fois je ne suis pas vraiment sûr, que je n'ai pas trouvé de source crédible de JLS, ou d'un autre fonctionnaire de référence qui parle de ce problème. Je vais continuer à chercher, et si j'arrive à en trouver un, permettra de mettre à jour la réponse.


Nous allons truc le compilateur par type explicite de l'information:

Si vous modifiez l'invocation de la méthode pour donner explicite des informations de type:

MyContainer<IntWrapper> test = converter.<IntWrapper>pack(1, "Test", "Test2");

Maintenant, le type K sera déduit qu' IntWrapper, mais depuis 1 n'est pas convertible en IntWrapper, cette méthode est abandonnée, et le 2e de la méthode invoke et il fonctionne parfaitement bien.


Franchement, je ne sais vraiment pas ce qui se passe ici. Je m'attends à ce que le compilateur de déduire le type de paramètre à partir de l'invocation de la méthode du contexte dans le premier cas également, comme il travaille 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 n'est pas en train de faire dans ce cas. Donc cela peut éventuellement être un bug.

*Ou peut-être que je ne comprends pas exactement comment le Compilateur déduit le type des arguments, quand le type n'est pas passée en argument. Donc, pour en apprendre plus à ce sujet, j'ai essayé de passer par - JLS §15.12.2.7 et JLS §15.12.2.8, qui est sur la façon dont le compilateur déduit le type de l'argument, mais qui va complètement sur le très haut de ma tête.

Donc, pour l'instant, vous avez à vivre avec elle, et utiliser l'autre (fourniture de type explicite argument).


Il s'avère que le Compilateur n'a pas jouer à n'importe quel truc:

Comme enfin expliqué dans le commentaire de @zhong.j.yu., compilateur ne s'applique section 15.12.2.8 pour l'inférence de type, quand il ne parvient pas à déduire comme par 15.12.2.7 section. Mais ici, il est possible de déduire le type Integer de l'argument étant passé, comme l'indique clairement le type de paramètre est un paramètre format de 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.

3voto

dharam Points 2107

Ici, vous allez, la différence entre le ci-dessous deux méthodes: 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(""));
}

Méthode 2 est aussi bon que

public MyContainer<IntWrapper> pack(Object ... values) {
    return new MyContainer<>(new IntWrapper(""));
 }

C'est pourquoi vous obtenez une ambiguïté..

MODIFIER Oui, je veux dire qu'ils sont les mêmes pour les compiler. Le but de l'utilisation de la variable d'arguments est de permettre à un utilisateur de définir une méthode quand il/elle est pas sûr de l' le nombre d'arguments d'un type donné.

Donc, si vous utilisez un objet en tant que variable d'arguments, tu viens de dire au compilateur que je ne suis pas sûr de savoir comment de nombreux objets, je vous l'enverrai, et d'autre part, vous dites,"je suis de passage d'un nombre entier et un nombre inconnu d'objets". Pour le compilateur, l'entier est un objet.

Si vous voulez vérifier la validité essayer de passer un entier comme premier argument et puis de passer un argument variable de Chaîne de caractères. Vous verrez la différence.

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");
}
}

Aussi, s'il vous plaît ne pas utiliser les tableaux et variable args de façon interchangeable, ils ont des fins différentes au total.

Lorsque vous utilisez varargs la méthode n'a pas s'attendre à un tableau, mais les différents paramètres de même type, qui peut être consulté dans indexé de la manière.

3voto

bayou.io Points 3680

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 Correspondant Arité de Méthodes Applicables par l'Invocation de Méthode de Conversion

m(2) satisfait 15.12.2.2. Phase 1: Identifier Correspondant Arité de Méthodes Applicables par sous-typage

Le compilateur s'arrête à la Phase 1; il a trouvé m(2) comme la seule méthode applicable à cette phase, donc m(2) est choisi.

Dans le var arg cas

(3) m(K,   String...)
(4) m(int, String...)

m(1, str1, str2);

Les deux m(3) et m(4) pour satisfaire à 15.12.2.4. Phase 3: Identifier Les Variables Arité De Méthodes . Ni est plus spécifique que l'autre, donc l'ambiguïté.

On peut regrouper les méthodes applicables en 4 groupes:

  1. échéant, par le sous-typage
  2. applicable par l'invocation de méthode de conversion
  3. vararg, applicable par sous-typage
  4. vararg, applicable par l'invocation de méthode de conversion

La spec groupe issu de la fusion de 3 et 4 et de les traiter à la fois dans la Phase 3. Par conséquent, l'incohérence.

Pourquoi ont-ils le faire? Maye, elles se fatigué de il.

Une autre critique serait que, il ne devrait pas être toutes ces phases, parce que les programmeurs ne pense pas de cette façon. Nous devons simplement trouver toutes les méthodes applicables indistinctement, puis choisissez le plus spécifique (avec un mécanisme pour éviter boxing/unboxing)

0voto

LastFreeNickname Points 490

Tout d'abord, ce n'est que quelques premiers indices... peut modifier pour plus d'.

Le compilateur toujours à la recherche et sélectionne le plus spécifique de la méthode. Bien qu'un peu lourde à lire, tout est spécifié dans JLS 15.12.2.5. Ainsi, en appelant

convertisseur de.pack(1, "Test", "Test2" )

ce n'est pas déterminable pour le compilateur de savoir si l' 1 doit être dissous pour K ou int. En d'autres termes, K peut s'appliquer à n'importe quel type, il est donc au même niveau que int/Integer.

La différence réside dans le nombre et le type des arguments. Considérer qu' new String[]{"Test", "Test2"} est un tableau, alors que "Test", "Test2" sont deux arguments de type Chaîne de caractères!

convertisseur de.pack(1); // ambiguës, erreur du compilateur

convertisseur de.pack(1, null); // appelle la méthode 2, avertissement du compilateur

convertisseur de.pack(1, new String[]{}); // appelle la méthode 2, avertissement du compilateur

convertisseur de.pack(1, new Object());// ambiguës, erreur du compilateur

convertisseur de.pack(1, new Object[]{});// appelle la méthode 2, pas d'avertissement

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