Perché questo lambda Java 8 non viene compilato?


85

Il seguente codice Java non viene compilato:

@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
}

Il compilatore riporta:

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

La cosa strana è che la riga contrassegnata con "OK" viene compilata correttamente, ma la riga contrassegnata con "Errore" non riesce. Sembrano essenzialmente identici.


5
è un errore di battitura qui che il metodo dell'interfaccia funzionale restituisce void?
Nathan Hughes

6
@NathanHughes No. Risulta essere centrale per la domanda - vedi la risposta accettata.
Brian Gordon

dovrebbe esserci codice all'interno { }di takeBiConsumer... e se è così, potresti fare un esempio ... se leggo correttamente, bcè un'istanza della classe / interfaccia BiConsumer, e quindi dovrebbe contenere un metodo chiamato acceptper abbinare la firma dell'interfaccia. .. ... e se è corretto, allora il acceptmetodo deve essere definito da qualche parte (ad esempio una classe che implementa l'interfaccia) ... quindi è quello che dovrebbe essere nel {}?? ... ... ... grazie
dsdsdsdsd

Le interfacce con un singolo metodo sono intercambiabili con lambda in Java 8. In questo caso, (String s1, String s2) -> "hi"è un'istanza di BiConsumer <String, String>.
Brian Gordon

Risposte:


100

Il tuo lambda deve essere congruente con BiConsumer<String, String>. Se fai riferimento a JLS # 15.27.3 (Type of a Lambda) :

Un'espressione lambda è congruente con un tipo di funzione se tutte le seguenti condizioni sono vere:

  • [...]
  • Se il risultato del tipo di funzione è void, il corpo lambda può essere un'espressione di istruzione (§14.8) o un blocco compatibile con il void.

Quindi lambda deve essere un'espressione di istruzione o un blocco compatibile con il vuoto:

  • Una chiamata di costruttore è un'espressione di istruzione, quindi viene compilata.
  • Una stringa letterale non è un'espressione di istruzione e non è compatibile con i void (cfr. Gli esempi in 15.27.2 ), quindi non viene compilata.

31
@BrianGordon Una stringa letterale è un'espressione (un'espressione costante per essere precisi) ma non un'espressione di istruzione.
assylias

44

Fondamentalmente, new String("hi")è un pezzo di codice eseguibile che effettivamente fa qualcosa (crea una nuova stringa e poi la restituisce). Il valore restituito può essere ignorato e new String("hi")può ancora essere utilizzato in lambda void-return per creare una nuova stringa.

Tuttavia, "hi"è solo una costante che non fa nulla da sola. L'unica cosa ragionevole da fare con esso nel corpo lambda è restituirlo . Ma il metodo lambda dovrebbe avere il tipo di ritorno Stringo Object, ma restituisce void, da qui l' String cannot be casted to voiderrore.


6
Il termine formale corretto è Expression Statement , un'espressione di creazione di istanze può apparire in entrambi i punti, dove è richiesta un'espressione o dove è richiesta un'istruzione, mentre un Stringletterale è solo un'espressione che non può essere utilizzata in un contesto di istruzione .
Holger

2
La risposta accettata potrebbe essere formalmente corretta, ma questa è una spiegazione migliore
edc65

3
@ edc65: ecco perché anche questa risposta è stata votata. Il ragionamento per le regole e la spiegazione intuitiva non formale può effettivamente aiutare, tuttavia, ogni programmatore dovrebbe essere consapevole che ci sono regole formali dietro di esso e nel caso in cui il risultato della regola formale non sia intuitivamente comprensibile, la regola formale vince comunque . Ad esempio, ()->x++è legale, mentre ()->(x++), fondamentalmente, fare esattamente lo stesso, non è ...
Holger

21

Il primo caso è ok perché stai invocando un metodo "speciale" (un costruttore) e non stai effettivamente prendendo l'oggetto creato. Solo per renderlo più chiaro, inserirò le parentesi graffe opzionali nei tuoi lambda:

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

E più chiaro, lo tradurrò nella vecchia notazione:

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

Nel primo caso stai eseguendo un costruttore, ma NON stai restituendo l'oggetto creato, nel secondo caso stai tentando di restituire un valore String, ma il tuo metodo nella tua interfaccia BiConsumerrestituisce void, da qui l'errore del compilatore.


12

Il JLS lo specifica

Se il risultato del tipo di funzione è void, il corpo lambda può essere un'espressione di istruzione (§14.8) o un blocco compatibile con il void.

Vediamolo ora in dettaglio,

Poiché il tuo takeBiConsumermetodo è di tipo void, la ricezione lambda new String("hi")lo interpreterà come un blocco simile

{
    new String("hi");
}

che è valido in un vuoto, da qui la compilazione del primo caso.

Tuttavia, nel caso in cui si trovi lambda -> "hi", un blocco come

{
    "hi";
}

non è una sintassi valida in java. Quindi l'unica cosa da fare con "ciao" è provare a restituirlo.

{
    return "hi";
}

che non è valido in un vuoto e spiega il messaggio di errore

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

Per una migliore comprensione, nota che se cambi il tipo di takeBiConsumerin String, -> "hi"sarà valido in quanto cercherà semplicemente di restituire direttamente la stringa.


Nota che all'inizio ho pensato che l'errore fosse causato dal fatto che lambda si trova in un contesto di invocazione sbagliato, quindi condividerò questa possibilità con la comunità:

JLS 15.27

È un errore in fase di compilazione se un'espressione lambda si verifica in un programma in un luogo diverso da un contesto di assegnazione (§5.2), un contesto di invocazione (§5.3) o un contesto di casting (§5.5).

Tuttavia, nel nostro caso, siamo in un contesto di invocazione corretto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.