write seqio read descargar java lambda java-8 method-overloading jls

seqio - Java 8 Consumer/Function Lambda Ambiguity



seqio read (1)

Tengo un método sobrecargado que toma un objeto Consumidor y una Función respectivamente y devuelve un tipo genérico que coincide con el Consumidor / Función correspondiente. Pensé que esto estaría bien, pero cuando trato de llamar a cualquiera de los dos métodos con una expresión lambda, aparece un error que indica que la referencia al método es ambigua.

Basado en mi lectura de JLS §15.12.2.1. Identificar métodos potencialmente aplicables: parece que el compilador debería saber que mi lambda con un bloque vacío coincide con el método del consumidor y mi lambda con un tipo de retorno coincide con el método de la función

Junté el siguiente código de ejemplo que no se compila:

import java.util.function.Consumer; import java.util.function.Function; public class AmbiguityBug { public static void main(String[] args) { doStuff(getPattern(x -> System.out.println(x))); doStuff(getPattern(x -> String.valueOf(x))); } static Pattern<String, String> getPattern(Function<String, String> function) { return new Pattern<>(function); } static ConsumablePattern<String> getPattern(Consumer<String> consumer) { return new ConsumablePattern<>(consumer); } static void doStuff(Pattern<String, String> pattern) { String result = pattern.apply("Hello World"); System.out.println(result); } static void doStuff(ConsumablePattern<String> consumablePattern) { consumablePattern.consume("Hello World"); } public static class Pattern<T, R> { private final Function<T, R> function; public Pattern(Function<T, R> function) { this.function = function; } public R apply(T value) { return function.apply(value); } } public static class ConsumablePattern<T> { private final Consumer<T> consumer; public ConsumablePattern(Consumer<T> consumer) { this.consumer = consumer; } public void consume(T value) { consumer.accept(value); } } }

También encontré una publicación similar stackoverflow que resultó ser un error de compilación. Mi caso es muy similar, aunque un poco más complicado. Para mí, esto todavía parece un error, pero quería asegurarme de que no estoy entendiendo mal la especificación de idioma para las lambdas. Estoy usando Java 8u45, que debería tener todas las correcciones más recientes.

Si cambio mis llamadas de método para que queden envueltas en un bloque, todo parece compilar, pero esto agrega verbosidad adicional y muchos auto-formateadores lo reformatearán en múltiples líneas.

doStuff(getPattern(x -> { System.out.println(x); })); doStuff(getPattern(x -> { return String.valueOf(x); }));


Esta línea es definitivamente ambigua:

doStuff(getPattern(x -> String.valueOf(x)));

Vuelva a leer esto desde el capítulo de JLS vinculado:

Una expresión lambda (§15.27) es potencialmente compatible con un tipo de interfaz funcional (§9.8) si se cumplen todas las siguientes condiciones:

  • La aridad del tipo de función del tipo de destino es la misma que la aridad de la expresión lambda.

  • Si el tipo de función del tipo de destino tiene un retorno de vacío, entonces el cuerpo lambda es una expresión de instrucción (§14.8) o un bloque compatible con el vacío (§15.27.2).

  • Si el tipo de función del tipo de destino tiene un tipo de retorno (no vacío), entonces el cuerpo lambda es una expresión o un bloque compatible con valores (§15.27.2).

En su caso para el Consumer , tiene una expresión de declaración, ya que cualquier invocación de método puede usarse como expresión de declaración, incluso si el método no es válido. Por ejemplo, simplemente puede escribir esto:

public void test(Object x) { String.valueOf(x); }

No tiene sentido, pero compila perfectamente. Su método puede tener un efecto secundario, el compilador no lo sabe. Por ejemplo, si fuera List.add que siempre devuelve true y a nadie le importa su valor de retorno.

Por supuesto, esta lambda también califica para la Function ya que es una expresión. Por eso es ambigua. Si tiene algo que es una expresión, pero no una expresión de declaración , entonces la llamada se asignará a la Function sin ningún problema:

doStuff(getPattern(x -> x == null ? "" : String.valueOf(x)));

Cuando lo cambias a { return String.valueOf(x); } { return String.valueOf(x); } , crea un bloque compatible con el valor , por lo que coincide con la Function , pero no califica como un bloque compatible con el vacío . Sin embargo, también puede tener problemas con los bloques:

doStuff(getPattern(x -> {throw new UnsupportedOperationException();}));

Este bloque califica como un valor compatible y un vacío, por lo que tiene una ambigüedad de nuevo. Otro ejemplo de bloque de ambigue es un bucle sin fin:

doStuff(getPattern(x -> {while(true) System.out.println(x);}));

En cuanto al caso System.out.println(x) es un poco complicado. Seguramente se califica como expresión de declaración , por lo que se puede hacer coincidir con el Consumer , pero parece que coincide con la expresión, así como las especificaciones dicen que la invocación del método es una expresión. Sin embargo, es una expresión de uso limitado como 15.12.3 dice:

Si la declaración de tiempo de compilación es nula, entonces la invocación del método debe ser una expresión de nivel superior (es decir, la expresión en una declaración de expresión o en la parte ForInit o ForUpdate de una instrucción for), o se produce un error en tiempo de compilación. Dicha invocación del método no produce ningún valor y, por lo tanto, solo debe utilizarse en una situación en la que no se necesita un valor.

Así que el compilador sigue perfectamente la especificación. Primero, determina que su cuerpo lambda está calificado como una expresión (aunque su tipo de devolución sea nula: 15.12.2.1 no es una excepción para este caso) y una expresión de declaración, por lo que también se considera una ambigüedad.

Por lo tanto, para mí, ambas declaraciones se compilan de acuerdo con la especificación. El compilador ECJ produce los mismos mensajes de error en este código.

En general, le sugiero que evite sobrecargar sus métodos cuando sus sobrecargas tienen el mismo número de parámetros y tienen la diferencia solo en la interfaz funcional aceptada. Incluso si estas interfaces funcionales tienen una aridad diferente (por ejemplo, Consumer y BiConsumer ): no tendrá problemas con lambda, pero puede tener problemas con las referencias de los métodos. Simplemente seleccione diferentes nombres para sus métodos en este caso (por ejemplo, processStuff y consumeStuff ).