Eu estava respondendo uma pergunta e me deparei com um cenário que não consigo explicar. Considere este código:
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
}
}
Eu não entendo por que digitar explicitamente o parâmetro do lambda (A a) -> aList.add(a)
faz o código compilar. Além disso, por que ele está vinculado à sobrecarga em Iterable
vez da que está dentro CustomIterable
?
Existe alguma explicação para isso ou um link para a seção relevante da especificação?
Nota: iterable.forEach((A a) -> aList.add(a));
somente compila quando CustomIterable<T>
estende Iterable<T>
(sobrecarregar os métodos de forma plana CustomIterable
resulta em erro ambíguo)
Obtendo isso em ambos:
- openjdk versão "13.0.2" 2020-01-14
Compilador Eclipse - openjdk versão "1.8.0_232"
Compilador Eclipse
Edit : O código acima falha ao compilar na construção com o maven, enquanto o Eclipse compila a última linha do código com êxito.
Respostas:
TL; DR, este é um bug do compilador.
Não há uma regra que daria precedência a um método aplicável específico quando ele é herdado ou um método padrão. Curiosamente, quando eu mudo o código para
a
iterable.forEach((A a) -> aList.add(a));
instrução produz um erro no Eclipse.Como nenhuma propriedade do
forEach(Consumer<? super T) c)
método daIterable<T>
interface foi alterada ao declarar outra sobrecarga, a decisão do Eclipse de selecionar esse método não pode ser (consistentemente) baseada em nenhuma propriedade do método. Ainda é o único método herdado, ainda é o únicodefault
método, ainda é o único método JDK e assim por diante. Nenhuma dessas propriedades deve afetar a seleção do método de qualquer maneira.Observe que alterar a declaração para
também produz um erro "ambíguo"; portanto, o número de métodos sobrecarregados aplicáveis também não importa, mesmo quando há apenas dois candidatos, não há preferência geral pelos
default
métodos.Até agora, o problema parece surgir quando há dois métodos aplicáveis e um
default
método e uma relação de herança estão envolvidos, mas esse não é o lugar certo para aprofundar.Mas é compreensível que as construções do seu exemplo possam ser tratadas por diferentes códigos de implementação no compilador, um exibindo um bug enquanto o outro não.
a -> aList.add(a)
é uma expressão lambda de tipo implícito , que não pode ser usada para a resolução de sobrecarga. Por outro lado,(A a) -> aList.add(a)
é uma expressão lambda de tipo explícito que pode ser usada para selecionar um método correspondente dos métodos sobrecarregados, mas não ajuda aqui (não deve ajudar aqui), pois todos os métodos têm tipos de parâmetros com exatamente a mesma assinatura funcional .Como um contra-exemplo, com
as assinaturas funcionais diferem e o uso de uma expressão lambda do tipo explicitamente pode realmente ajudar a selecionar o método certo, enquanto a expressão lambda implicitamente digitada não ajuda, portanto
forEach(s -> s.isEmpty())
produz um erro do compilador. E todos os compiladores Java concordam com isso.Observe que
aList::add
é uma referência de método ambígua, pois oadd
método também está sobrecarregado, por isso também não pode ajudar na seleção de um método, mas as referências a métodos podem ser processadas por código diferente de qualquer maneira. Mudar para um não ambíguoaList::contains
ou mudarList
paraCollection
, para tornaradd
não ambíguo, não alterou o resultado na minha instalação do Eclipse (eu usei2019-06
).fonte
default
método é apenas um ponto adicional. Minha resposta já mostra um exemplo em que o Eclipse não dá precedência ao método padrão.default
altera o resultado e imediatamente assume que encontrou o motivo do comportamento observado. Você é tão confiante demais que chama outras respostas erradas, apesar de elas nem contradizerem. Como você está projetando seu próprio comportamento em detrimento de outro , nunca reivindiquei que a herança fosse a razão . Eu provei que não é. Eu demonstrei que o comportamento é inconsistente, pois o Eclipse seleciona o método específico em um cenário, mas não em outro, onde existem três sobrecargas.default
o outroabstract
, com dois argumentos de consumidor e experimente. O Eclipse diz corretamente que é ambíguo, apesar de um ser umdefault
método. Aparentemente, a herança ainda é relevante para esse bug do Eclipse, mas, diferentemente de você, não estou enlouquecendo e chamo outras respostas de erradas, apenas porque elas não analisaram o bug por completo. Esse não é o nosso trabalho aqui.O compilador Eclipse resolve corretamente o
default
método , pois esse é o método mais específico de acordo com a Java Language Specification 15.12.2.5 :javac
(usado por Maven e IntelliJ por padrão) informa que a chamada do método é ambígua aqui. Mas, de acordo com a Java Language Specification, não é ambíguo, pois um dos dois métodos é o método mais específico aqui.Expressões lambda de tipo implícito são tratadas de maneira diferente das expressões lambda de tipo explícito em Java. Digitar implicitamente em contraste com expressões lambda explicitamente digitadas passa pela primeira fase para identificar os métodos de chamada estrita (consulte Java Language Specification jls-15.12.2.2 , primeiro ponto). Portanto, a chamada de método aqui é ambígua para expressões lambda implicitamente digitadas .
No seu caso, a solução alternativa para esse
javac
erro é especificar o tipo da interface funcional em vez de usar uma expressão lambda explicitamente digitada da seguinte maneira:ou
Aqui está o seu exemplo ainda mais minimizado para testes:
fonte
default
método quando há três métodos candidatos ou quando os dois métodos são declarados na mesma interface.forEach(Consumer)
eforEach(Consumer2)
que nunca podem terminar no mesmo método de implementação.O código no qual o Eclipse implementa o JLS §15.12.2.5 não encontra nenhum método como mais específico que o outro, mesmo no caso da lambda explicitamente digitada.
Então, idealmente, o Eclipse para aqui e informa ambigüidade. Infelizmente, a implementação da resolução de sobrecarga possui código não trivial, além de implementar o JLS. Pelo meu entendimento, esse código (que data da época em que o Java 5 era novo) deve ser mantido para preencher algumas lacunas no JLS.
Eu enviei https://bugs.eclipse.org/562538 para rastrear isso.
Independente desse bug em particular, só posso aconselhar fortemente contra esse estilo de código. Sobrecarregar é bom para um bom número de surpresas em Java, multiplicado pela inferência do tipo lambda, a complexidade é desproporcional ao ganho percebido.
fonte