Je répondais à une question et suis tombé sur un scénario que je ne peux pas expliquer. Considérez ce code:
interface ConsumerOne<T> {
void accept(T a);
}
interface CustomIterable<T> extends Iterable<T> {
void forEach(ConsumerOne<? super T> c); //overload
}
class A {
private static CustomIterable<A> iterable;
private static List<A> aList;
public static void main(String[] args) {
iterable.forEach(a -> aList.add(a)); //ambiguous
iterable.forEach(aList::add); //ambiguous
iterable.forEach((A a) -> aList.add(a)); //OK
}
}
Je ne comprends pas pourquoi taper explicitement le paramètre du lambda (A a) -> aList.add(a)
fait compiler le code. De plus, pourquoi est-il lié à la surcharge dans Iterable
plutôt qu'à celle dedans CustomIterable
?
Y a-t-il une explication à cela ou un lien vers la section pertinente de la spécification?
Remarque: iterable.forEach((A a) -> aList.add(a));
compile uniquement lors de l' CustomIterable<T>
extension Iterable<T>
(une surcharge plate des méthodes CustomIterable
entraîne une erreur ambiguë)
Obtenir ceci sur les deux:
- openjdk version "13.0.2" 2020-01-14
Compilateur Eclipse - openjdk version "1.8.0_232"
compilateur Eclipse
Edit : le code ci-dessus ne parvient pas à compiler lors de la construction avec maven tandis qu'Eclipse compile la dernière ligne de code avec succès.
Réponses:
TL; DR, il s'agit d'un bogue du compilateur.
Il n'y a aucune règle qui donnerait la priorité à une méthode applicable particulière lorsqu'elle est héritée ou à une méthode par défaut. Fait intéressant, lorsque je change le code en
l'
iterable.forEach((A a) -> aList.add(a));
instruction produit une erreur dans Eclipse.Puisqu'aucune propriété de la
forEach(Consumer<? super T) c)
méthode de l'Iterable<T>
interface n'a changé lors de la déclaration d'une autre surcharge, la décision d'Eclipse de sélectionner cette méthode ne peut pas (de manière cohérente) être basée sur une propriété de la méthode. C'est toujours la seule méthode héritée, toujours la seuledefault
méthode, toujours la seule méthode JDK, etc. Aucune de ces propriétés ne devrait de toute façon affecter la sélection de la méthode.Notez que la modification de la déclaration en
produit également une erreur «ambiguë», donc le nombre de méthodes surchargées applicables n'a pas d'importance non plus, même lorsqu'il n'y a que deux candidats, il n'y a pas de préférence générale vers les
default
méthodes.Jusqu'à présent, le problème semble se poser lorsqu'il existe deux méthodes applicables et qu'une
default
méthode et une relation d'héritage sont impliquées, mais ce n'est pas le bon endroit pour creuser davantage.Mais il est compréhensible que les constructions de votre exemple puissent être gérées par différents codes d'implémentation dans le compilateur, l'un présentant un bogue tandis que l'autre ne le fait pas.
a -> aList.add(a)
est une expression lambda typée implicitement , qui ne peut pas être utilisée pour la résolution de surcharge. En revanche,(A a) -> aList.add(a)
est une expression lambda explicitement typée qui peut être utilisée pour sélectionner une méthode correspondante parmi les méthodes surchargées, mais cela n'aide pas ici (ne devrait pas aider ici), car toutes les méthodes ont des types de paramètres avec exactement la même signature fonctionnelle .À titre de contre-exemple, avec
les signatures fonctionnelles diffèrent, et l'utilisation d'une expression lambda de type explicite peut en effet aider à sélectionner la bonne méthode tandis que l'expression lambda implicitement typée n'aide pas, donc
forEach(s -> s.isEmpty())
produit une erreur de compilation. Et tous les compilateurs Java sont d'accord là-dessus.Notez qu'il
aList::add
s'agit d'une référence de méthode ambiguë, car laadd
méthode est également surchargée, elle ne peut donc pas aider à sélectionner une méthode, mais les références de méthode peuvent de toute façon être traitées par un code différent. Passer à un sans ambiguïtéaList::contains
ou passerList
àCollection
, pour rendre l'add
ambiguïté, n'a pas changé le résultat dans mon installation Eclipse (j'ai utilisé2019-06
).la source
default
méthode n'est qu'un point supplémentaire. Ma réponse montre déjà un exemple où Eclipse ne donne pas la priorité à la méthode par défaut.default
modifiait le résultat et supposiez immédiatement trouver la raison du comportement observé. Vous êtes tellement confiant à ce sujet que vous appelez mal d'autres réponses, malgré le fait qu'elles ne sont même pas contradictoires. Parce que vous projetez votre propre comportement sur d'autres, je n'ai jamais prétendu que l'héritage était la raison . J'ai prouvé que non. J'ai démontré que le comportement est incohérent, car Eclipse sélectionne la méthode particulière dans un scénario mais pas dans un autre, où trois surcharges existent.default
l'autreabstract
, avec deux arguments de type consommateur et l'essayer. Eclipse dit à juste titre qu'elle est ambiguë, bien que l'une soit unedefault
méthode. Apparemment, l'héritage est toujours pertinent pour ce bogue Eclipse, mais contrairement à vous, je ne deviens pas fou et je ne me trompe pas sur les autres réponses, simplement parce qu'ils n'ont pas analysé le bogue dans son intégralité. Ce n'est pas notre travail ici.Le compilateur Eclipse se résout correctement à la
default
méthode , car il s'agit de la méthode la plus spécifique selon la spécification de langage Java 15.12.2.5 :javac
(utilisé par Maven et IntelliJ par défaut) indique que l'appel de méthode est ambigu ici. Mais selon la spécification du langage Java, ce n'est pas ambigu car l'une des deux méthodes est ici la méthode la plus spécifique.Les expressions lambda typées implicitement sont traitées différemment des expressions lambda typées explicitement en Java. Les expressions lambda typées implicitement, contrairement aux expressions explicitement typées, passent par la première phase pour identifier les méthodes d'invocation stricte (voir Java Language Specification jls-15.12.2.2 , premier point). Par conséquent, l'appel de méthode ici est ambigu pour les expressions lambda typées implicitement .
Dans votre cas, la solution de contournement pour ce
javac
bogue consiste à spécifier le type de l'interface fonctionnelle au lieu d'utiliser une expression lambda explicitement typée comme suit:ou
Voici votre exemple encore minimisé pour les tests:
la source
default
méthode lorsqu'il existe trois méthodes candidates ou lorsque les deux méthodes sont déclarées dans la même interface.forEach(Consumer)
etforEach(Consumer2)
qui ne peuvent jamais aboutir à la même méthode de mise en œuvre.Le code dans lequel Eclipse implémente JLS §15.12.2.5 ne trouve aucune méthode comme plus spécifique que l'autre, même pour le cas du lambda explicitement typé.
Idéalement, Eclipse s'arrêterait ici et rapporterait une ambiguïté. Malheureusement, l'implémentation de la résolution de surcharge a un code non trivial en plus d'implémenter JLS. D'après ma compréhension, ce code (qui date de l'époque où Java 5 était nouveau) doit être conservé pour combler certaines lacunes dans JLS.
J'ai déposé https://bugs.eclipse.org/562538 pour suivre cela.
Indépendamment de ce bug particulier, je ne peux que déconseiller fortement ce style de code. La surcharge est bonne pour un bon nombre de surprises en Java, multipliée par l'inférence de type lambda, la complexité est tout à fait disproportionnée par rapport au gain perçu.
la source