Estaba respondiendo una pregunta y me encontré con un escenario que no puedo explicar. Considera 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
}
}
No entiendo por qué escribir explícitamente el parámetro de lambda (A a) -> aList.add(a)
hace que el código se compile. Además, ¿por qué se vincula a la sobrecarga en Iterable
lugar de a la que está dentro CustomIterable
?
¿Hay alguna explicación para esto o un enlace a la sección relevante de la especificación?
Nota: iterable.forEach((A a) -> aList.add(a));
solo se compila cuando se CustomIterable<T>
extiende Iterable<T>
(sobrecargando completamente los métodos en CustomIterable
resultados en el error ambiguo)
Conseguir esto en ambos:
- openjdk versión "13.0.2" 2020-01-14
Compilador Eclipse - Openjdk versión "1.8.0_232"
compilador Eclipse
Editar : el código anterior no se compila al compilar con maven mientras Eclipse compila la última línea de código con éxito.
Respuestas:
TL; DR, este es un error del compilador.
No existe una regla que otorgue prioridad a un método aplicable en particular cuando se hereda o un método predeterminado. Curiosamente, cuando cambio el código a
La
iterable.forEach((A a) -> aList.add(a));
declaración produce un error en Eclipse.Como ninguna propiedad del
forEach(Consumer<? super T) c)
método desde laIterable<T>
interfaz cambió cuando se declara otra sobrecarga, la decisión de Eclipse de seleccionar este método no puede basarse (de manera consistente) en ninguna propiedad del método. Sigue siendo el único método heredado, sigue siendo el únicodefault
método, sigue siendo el único método JDK, etc. Ninguna de estas propiedades debería afectar la selección del método de todos modos.Tenga en cuenta que cambiar la declaración a
también produce un error "ambiguo", por lo que el número de métodos sobrecargados aplicables tampoco importa, incluso cuando solo hay dos candidatos, no hay preferencia general hacia los
default
métodos.Hasta ahora, el problema parece aparecer cuando hay dos métodos aplicables y un
default
método y una relación de herencia están involucrados, pero este no es el lugar correcto para profundizar.Pero es comprensible que las construcciones de su ejemplo puedan ser manejadas por diferentes códigos de implementación en el compilador, uno exhibiendo un error mientras que el otro no.
a -> aList.add(a)
es una expresión lambda escrita implícitamente , que no se puede usar para la resolución de sobrecarga. Por el contrario,(A a) -> aList.add(a)
es una expresión lambda escrita explícitamente que se puede usar para seleccionar un método coincidente de los métodos sobrecargados, pero no ayuda aquí (no debería ayudar aquí), ya que todos los métodos tienen tipos de parámetros con exactamente la misma firma funcional .Como contraejemplo, con
las firmas funcionales difieren, y el uso de una expresión lambda de tipo explícito puede ayudar a seleccionar el método correcto, mientras que la expresión lambda escrita implícitamente no ayuda, por lo que
forEach(s -> s.isEmpty())
produce un error de compilación. Y todos los compiladores de Java están de acuerdo con eso.Tenga en cuenta que
aList::add
es una referencia de método ambigua, ya que eladd
método también está sobrecargado, por lo que tampoco puede ayudar a seleccionar un método, pero las referencias de método pueden ser procesadas por un código diferente de todos modos. El cambio a una ambiguaaList::contains
o cambiarList
aCollection
, para queadd
no ambigua, no cambió el resultado en mi instalación de Eclipse (utilicé2019-06
).fuente
default
método es solo un punto adicional. Mi respuesta ya muestra un ejemplo donde Eclipse no da prioridad al método predeterminado.default
cambia el resultado e inmediatamente asume que encontró la razón del comportamiento observado. Estás tan confiado al respecto que llamas a otras respuestas incorrectas, a pesar de que ni siquiera son contradictorias. Debido a que está proyectando su propio comportamiento sobre otro , nunca alegué que la herencia fuera la razón . Probé que no lo es. Demostré que el comportamiento es inconsistente, ya que Eclipse selecciona el método particular en un escenario pero no en otro, donde existen tres sobrecargas.default
el otroabstract
, con dos argumentos similares para el consumidor y probarlo. Eclipse dice correctamente que es ambiguo, a pesar de que uno es undefault
método. Aparentemente, la herencia sigue siendo relevante para este error de Eclipse, pero a diferencia de usted, no me estoy volviendo loco y digo que otras respuestas son incorrectas, solo porque no analizaron el error en su totalidad. Ese no es nuestro trabajo aquí.El compilador de Eclipse resuelve correctamente el
default
método , ya que este es el método más específico de acuerdo con la Especificación de lenguaje Java 15.12.2.5 :javac
(utilizado por Maven e IntelliJ por defecto) indica que la llamada al método es ambigua aquí. Pero de acuerdo con la especificación del lenguaje Java, no es ambiguo ya que uno de los dos métodos es el método más específico aquí.Las expresiones lambda escritas implícitamente se manejan de manera diferente a las expresiones lambda escritas explícitamente en Java. Tipeado implícitamente en contraste con las expresiones lambda tipeadas explícitamente pasan por la primera fase para identificar los métodos de invocación estricta (consulte la Especificación del lenguaje Java jls-15.12.2.2 , primer punto). Por lo tanto, la llamada al método aquí es ambigua para expresiones lambda escritas implícitamente .
En su caso, la solución para este
javac
error es especificar el tipo de la interfaz funcional en lugar de usar una expresión lambda escrita explícitamente de la siguiente manera:o
Aquí está su ejemplo minimizado para pruebas:
fuente
default
método cuando hay tres métodos candidatos o cuando ambos métodos se declaran en la misma interfaz.forEach(Consumer)
yforEach(Consumer2)
que nunca pueden terminar en el mismo método de implementación.El código donde Eclipse implementa JLS §15.12.2.5 no encuentra ninguno de los métodos como más específico que el otro, incluso para el caso de la lambda escrita explícitamente.
Así que, idealmente, Eclipse se detendría aquí e informaría de ambigüedad. Desafortunadamente, la implementación de la resolución de sobrecarga tiene un código no trivial además de implementar JLS. Según tengo entendido, este código (que data del momento en que Java 5 era nuevo) debe mantenerse para llenar algunos vacíos en JLS.
He presentado https://bugs.eclipse.org/562538 para rastrear esto.
Independientemente de este error en particular, solo puedo desaconsejar este estilo de código. La sobrecarga es buena para una buena cantidad de sorpresas en Java, multiplicada por la inferencia de tipo lambda, la complejidad es bastante desproporcionada con la ganancia percibida.
fuente