2015-03-25 17 views
81

Il seguente codice Java non riesce a compilare:Perché questo Java 8 lambda non riesce a compilare?

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

Le relazioni del compilatore:

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 linea marcata "OK" compila bene, ma la linea marcata "errore" non riesce . Sembrano essenzialmente identici.

+5

è un errore quando il metodo dell'interfaccia funzionale restituisce void? –

+6

@NathanHughes Nope. Risulta essere centrale nella domanda- vedere la risposta accettata. –

+0

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

risposta

98

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

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

  • [...]
  • Se il risultato della tipo di funzione è vuoto , il corpo lambda è un'espressione di dichiarazione (§14.8) o un blocco compatibile con il vuoto.

Così il lambda deve essere sia un'espressione un'istruzione o un blocco compatibile vuoto:

+30

@BrianGordon Un letterale stringa è un'espressione (un'espressione costante per essere precisi) ma non un espressione di dichiarazione. – assylias

11

Il JLS specificare che

Se il risultato della funzione è di tipo vuoto, il corpo lambda è sia un'espressione dichiarazione (§14.8) o di un blocco di vuoto-compatibili.

Ora vediamo che nel dettaglio,

Dal momento che il metodo takeBiConsumer è di tipo void, il Lambda riceve new String("hi") lo interpreterà come un blocco come

{ 
    new String("hi"); 
} 

che è valido in un vuoto , quindi il primo caso compilato.

Tuttavia, nel caso in cui il lambda è -> "hi", un blocco come

{ 
    "hi"; 
} 

non è valido per la sintassi Java. Quindi l'unica cosa da fare con "ciao" è provare e restituirlo.

{ 
    return "hi"; 
} 

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

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

Per una migliore comprensione, si noti che se si cambia il tipo di takeBiConsumer in una stringa, -> "hi" sarà valida come proverà semplicemente a restituire direttamente la stringa.


Si noti che in un primo momento ho insegnato l'errore è stato causato dal lambda essere in un contesto invocazione sbagliato, così io condividere questa possibilità con la comunità:

JLS 15.27

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

Tuttavia nel nostro caso, siamo in un invocation context che è corretto.

21

Il primo caso è ok perché si sta invocando un metodo "speciale" (un costruttore) e non si sta effettivamente prendendo l'oggetto creato.Giusto per renderlo più chiaro, io metterò le parentesi graffe opzionali nel lambda:

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

E più chiaro, io traduco che per la 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 nell'interfaccia BiConsumer restituisce nulla, quindi l'errore del compilatore.

42

In linea di principio, new String("hi") è una parte di codice eseguibile che esegue effettivamente qualcosa (crea una nuova stringa e quindi 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 per conto proprio. L'unica cosa ragionevole da fare con il corpo lambda è restituire it. Ma il metodo lambda dovrebbe avere il tipo di ritorno String o Object, ma restituisce void, quindi l'errore String cannot be casted to void.

+6

Il termine formale corretto è [* Dichiarazione espressione *] (http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.8), potrebbe apparire un'espressione di creazione dell'istanza in entrambe le posizioni, dove un'espressione o dove è richiesta un'istruzione, mentre un letterale 'String' è solo una * espressione * che non può essere utilizzata in un contesto * statement *. – Holger

+1

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

+3

@ edc65: ecco perché anche questa risposta è stata upvoted. Il ragionamento per le regole e la spiegazione intuitiva non formale può davvero aiutare, tuttavia, ogni programmatore dovrebbe essere consapevole del fatto che ci sono regole formali dietro di esso e nel caso in cui l'esito della regola formale è * non * intuitivamente comprensibile, la regola formale vince ancora. Per esempio. '() -> x ++' è legale, mentre '() -> (x ++)', praticamente facendo esattamente lo stesso, non è ... – Holger

Problemi correlati