¿Por qué no se compila esta lambda de Java 8?

85

El siguiente código Java no se puede compilar:

@FunctionalInterface
private interface BiConsumer<A, B> {
    void accept(A a, B b);
}

private static void takeBiConsumer(BiConsumer<String, String> bc) { }

public static void main(String[] args) {
    takeBiConsumer((String s1, String s2) -> new String("hi")); // OK
    takeBiConsumer((String s1, String s2) -> "hi"); // Error
}

El compilador informa:

Error:(31, 58) java: incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void

Lo extraño es que la línea marcada como "OK" se compila bien, pero la línea marcada como "Error" falla. Parecen esencialmente idénticos.

Brian Gordon
fuente
5
¿Es un error tipográfico aquí que el método de interfaz funcional devuelva vacío?
Nathan Hughes
6
@NathanHughes No. Resulta ser fundamental para la pregunta: consulte la respuesta aceptada.
Brian Gordon
debería haber código dentro { }de takeBiConsumer... y si es así, ¿podría dar un ejemplo ... si leo esto correctamente, bces una instancia de la clase / interfaz BiConsumery, por lo tanto, debería contener un método llamado acceptpara que coincida con la firma de la interfaz? .. ... y si eso es correcto, entonces el acceptmétodo debe definirse en algún lugar (por ejemplo, una clase que implementa la interfaz) ... entonces, ¿qué debería estar en el {}?? ... ... ... Gracias
dsdsdsdsd
Las interfaces con un solo método son intercambiables con lambdas en Java 8. En este caso, (String s1, String s2) -> "hi"es una instancia de BiConsumer <String, String>.
Brian Gordon

Respuestas:

100

Tu lambda debe ser congruente con BiConsumer<String, String>. Si se refiere a JLS # 15.27.3 (Tipo de Lambda) :

Una expresión lambda es congruente con un tipo de función si todo lo siguiente es verdadero:

  • [...]
  • Si el resultado del tipo de función es nulo, el cuerpo lambda es una expresión de declaración (§14.8) o un bloque compatible con void.

Entonces, la lambda debe ser una expresión de declaración o un bloque compatible vacío:

Assylias
fuente
31
@BrianGordon Un literal de cadena es una expresión (una expresión constante para ser precisos) pero no una expresión de declaración.
assylias
44

Básicamente, new String("hi")es un fragmento de código ejecutable que realmente hace algo (crea una nueva cadena y luego la devuelve). El valor devuelto se puede ignorar ynew String("hi") aún se puede usar en la lambda void-return para crear una nueva cadena.

Sin embargo, "hi"es solo una constante que no hace nada por sí misma. Lo único razonable que se puede hacer con él en el cuerpo lambda es devolverlo . Pero el método lambda debería tener el tipo de retorno Stringo Object, pero devuelve void, de ahí el String cannot be casted to voiderror.

kajacx
fuente
6
El término formal correcto es Expression Statement , una expresión de creación de instancia puede aparecer en ambos lugares, donde se requiere una expresión o donde se requiere una declaración, mientras que un Stringliteral es solo una expresión que no se puede usar en un contexto de declaración .
Holger
2
La respuesta aceptada puede ser formalmente correcta, pero esta es una mejor explicación
edc65
3
@ edc65: es por eso que esta respuesta también fue votada a favor. El razonamiento de las reglas y la explicación intuitiva no formal pueden ayudar, sin embargo, todo programador debe ser consciente de que hay reglas formales detrás y, en el caso de que el resultado de la regla formal no sea intuitivamente comprensible, la regla formal seguirá ganando. . Por ejemplo, ()->x++es legal, mientras que ()->(x++), básicamente, hacer exactamente lo mismo, no es…
Holger
21

El primer caso está bien porque está invocando un método "especial" (un constructor) y en realidad no está tomando el objeto creado. Para que quede más claro, pondré las llaves opcionales en sus lambdas:

takeBiConsumer((String s1, String s2) -> {new String("hi");}); // OK
takeBiConsumer((String s1, String s2) -> {"hi"}); // Error

Y más claro, lo traduciré a la notación anterior:

takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
    public void accept(String s, String s2) {
        new String("hi"); // OK
    }
});

takeBiConsumer(new BiConsumer<String, String>(String s1, String s2) {
    public void accept(String s, String s2) {
        "hi"; // Here, the compiler will attempt to add a "return"
              // keyword before the "hi", but then it will fail
              // with "compiler error ... bla bla ...
              //  java.lang.String cannot be converted to void"
    }
});

En el primer caso, está ejecutando un constructor, pero NO está devolviendo el objeto creado, en el segundo caso está intentando devolver un valor de cadena, pero su método en su interfaz BiConsumerdevuelve vacío, de ahí el error del compilador.

morgano
fuente
12

El JLS especifica que

Si el resultado del tipo de función es nulo, el cuerpo lambda es una expresión de declaración (§14.8) o un bloque compatible con void.

Ahora veamos eso en detalle,

Dado que su takeBiConsumermétodo es de tipo vacío, la lambda que recibe new String("hi")lo interpretará como un bloque como

{
    new String("hi");
}

que es válido en un vacío, de ahí el primer caso compilado.

Sin embargo, en el caso donde está la lambda -> "hi", un bloque como

{
    "hi";
}

no es una sintaxis válida en java. Por lo tanto, lo único que se puede hacer con "hola" es intentar devolverlo.

{
    return "hi";
}

que no es válido en un vacío y explique el mensaje de error

incompatible types: bad return type in lambda expression
    java.lang.String cannot be converted to void

Para una mejor comprensión, tenga en cuenta que si cambia el tipo de takeBiConsumera String, -> "hi"será válido ya que simplemente intentará devolver directamente la cadena.


Tenga en cuenta que al principio pensé que el error se debió a que lambda estaba en un contexto de invocación incorrecto, por lo que compartiré esta posibilidad con la comunidad:

JLS 15.27

Es un error en tiempo de compilación si una expresión lambda ocurre en un programa en algún lugar que no sea un contexto de asignación (§5.2), un contexto de invocación (§5.3) o un contexto de conversión (§5.5).

Sin embargo, en nuestro caso, estamos en un contexto de invocación que es correcto.

Jean-François Savard
fuente