Por que este lambda Java 8 falha ao compilar?

85

O seguinte código Java falha ao 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
}

Os relatórios do compilador:

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

O estranho é que a linha marcada "OK" compila bem, mas a linha marcada "Erro" falha. Eles parecem essencialmente idênticos.

Brian Gordon
fonte
5
é um erro de digitação aqui que o método de interface funcional retorna void?
Nathan Hughes,
6
@NathanHughes Não. Acontece que é central para a pergunta - veja a resposta aceita.
Brian Gordon
deve haver código dentro do { }de takeBiConsumer... e se houver, você poderia dar um exemplo ... se eu li isso corretamente, bcé uma instância da classe / interface BiConsumere, portanto, deve conter um método chamado acceptpara corresponder à assinatura da interface. .. ... e se estiver certo, então o acceptmétodo precisa ser definido em algum lugar (por exemplo, uma classe que implementa a interface) ... então é isso que deve estar no {}?? ... ... ... obrigado
dsdsdsdsd
As interfaces com um único método são intercambiáveis ​​com lambdas em Java 8. Nesse caso, (String s1, String s2) -> "hi"é uma instância de BiConsumer <String, String>.
Brian Gordon

Respostas:

100

Seu lambda precisa ser congruente com BiConsumer<String, String>. Se você se referir a JLS # 15.27.3 (Tipo de Lambda) :

Uma expressão lambda é congruente com um tipo de função se todas as opções a seguir forem verdadeiras:

  • [...]
  • Se o resultado do tipo de função for nulo, o corpo lambda é uma expressão de instrução (§14.8) ou um bloco compatível com nulo.

Portanto, o lambda deve ser uma expressão de instrução ou um bloco compatível com void:

assilias
fonte
31
@BrianGordon Um literal String é uma expressão (uma expressão constante para ser mais preciso), mas não uma expressão de instrução.
assylias
44

Basicamente, new String("hi")é um trecho de código executável que realmente faz algo (ele cria uma nova String e a retorna). O valor retornado pode ser ignorado e new String("hi")ainda pode ser usado em lambda void-return para criar uma nova String.

No entanto, "hi"é apenas uma constante que não faz nada por conta própria. A única coisa razoável a fazer com ele no corpo lambda é devolvê- lo. Mas o método lambda teria que ter um tipo de retorno Stringou Object, mas ele retorna void, daí o String cannot be casted to voiderro.

Kajacx
fonte
6
O termo formal correto é Expression Statement , uma expressão de criação de instância pode aparecer em ambos os lugares, onde uma expressão ou onde uma instrução é necessária, enquanto um Stringliteral é apenas uma expressão que não pode ser usada em um contexto de instrução .
Holger
2
A resposta aceita pode ser formalmente correta, mas esta é uma explicação melhor
edc65
3
@ edc65: é por isso que essa resposta também foi votada positivamente. O raciocínio para as regras e a explicação intuitiva não formal podem de fato ajudar, no entanto, todo programador deve estar ciente de que existem regras formais por trás disso e no caso de o resultado da regra formal não ser intuitivamente compreensível, a regra formal ainda vencerá . Por exemplo, ()->x++é legal, enquanto ()->(x++), basicamente fazer exatamente o mesmo, não é ...
Holger
21

O primeiro caso está ok porque você está invocando um método "especial" (um construtor) e não está realmente pegando o objeto criado. Só para deixar mais claro, colocarei as chaves opcionais em seus lambdas:

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

E mais claro, vou traduzir isso para a notação mais antiga:

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"
    }
});

No primeiro caso, você está executando um construtor, mas NÃO está retornando o objeto criado; no segundo caso, você está tentando retornar um valor String, mas seu método em sua interface BiConsumerretorna void, daí o erro do compilador.

Morgano
fonte
12

O JLS especifica que

Se o resultado do tipo de função for nulo, o corpo lambda é uma expressão de instrução (§14.8) ou um bloco compatível com nulo.

Agora vamos ver isso em detalhes,

Uma vez que seu takeBiConsumermétodo é do tipo void, o receptor lambda new String("hi")irá interpretá-lo como um bloco como

{
    new String("hi");
}

que é válido em um vazio, daí a primeira compilação de caso.

No entanto, no caso em que o lambda é -> "hi", um bloco como

{
    "hi";
}

não é uma sintaxe válida em java. Portanto, a única coisa a fazer com "oi" é tentar devolvê-lo.

{
    return "hi";
}

que não é válido em um vazio e explica a mensagem de erro

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

Para um melhor entendimento, note que se você alterar o tipo de takeBiConsumerpara String, -> "hi"será válido pois simplesmente tentará retornar diretamente a string.


Observe que, a princípio, achei que o erro era causado pelo lambda estar em um contexto de invocação incorreto, portanto, compartilharei essa possibilidade com a comunidade:

JLS 15.27

É um erro em tempo de compilação se uma expressão lambda ocorre em um programa em algum lugar diferente de um contexto de atribuição (§5.2), um contexto de invocação (§5.3) ou um contexto de lançamento (§5.5).

No entanto, em nosso caso, estamos em um contexto de invocação correto.

Jean-François Savard
fonte