Bu Java 8 lambda neden derlenemiyor?


85

Aşağıdaki Java kodu derlenemiyor:

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

Derleyici şunları bildirir:

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

İşin garibi, "Tamam" olarak işaretlenen satırın iyi derlenmesi, ancak "Hata" olarak işaretlenen satırın başarısız olmasıdır. Temelde aynı görünüyorlar.


5
burada işlevsel arabirim yönteminin void döndürmesi bir yazım hatası mı?
Nathan Hughes

6
@NathanHughes Hayır. Sorunun merkezinde olduğu ortaya çıkıyor - kabul edilen cevaba bakın.
Brian Gordon

içeride kod olması gerektiğini { }ait takeBiConsumerbir örnek verebilir olabilir bunu doğru okursanız ..., ... ve eğer öyleyse bcsınıf / arayüzünün bir örneğidir BiConsumeradlı bir yöntem içermelidir böylece ve acceptarayüz imza eşleşecek. .. ... ve bu doğru ise, o zaman accept... yani ne olması gerektiğidir yöntem tanımlamak gerekir yerde (uygular arayüzünü o örn bir sınıfı) {}?? ... ... ... teşekkürler
dsdsdsdsd

Tek yöntemli arayüzler, Java 8'de lambdas ile değiştirilebilir. Bu durumda, (String s1, String s2) -> "hi"BiConsumer <String, String> örneğidir.
Brian Gordon

Yanıtlar:


100

Lambda'nızın uyumlu olması gerekir BiConsumer<String, String>. Eğer başvurursanız JLS # 15.27.3 (a Lambda Tip) :

Aşağıdakilerin tümü doğruysa, bir lambda ifadesi bir işlev türüyle uyumludur:

  • [...]
  • İşlev türünün sonucu void ise, lambda gövdesi ya bir ifade ifadesi (§14.8) ya da void uyumlu bir bloktur.

Dolayısıyla lambda ya bir ifade ifadesi ya da void uyumlu bir blok olmalıdır:


31
@BrianGordon Bir String değişmezi bir ifadedir (kesin olması için sabit bir ifade), ancak bir ifade ifadesi değildir.
asylias

44

Temel olarak, new String("hi")aslında bir şeyler yapan çalıştırılabilir bir kod parçasıdır (yeni bir Dize oluşturur ve sonra onu döndürür). Döndürülen değer göz ardı new String("hi")edilebilir ve void-return lambda'da yeni bir String oluşturmak için hala kullanılabilir.

Ancak, "hi"kendi başına hiçbir şey yapmayan bir sabittir. Lambda bedeninde bununla yapılabilecek tek makul şey onu iade etmektir . Ama lambda yöntemi dönüş türü olurdu Stringya Object, ama döner voiddolayısıyla String cannot be casted to voidhata.


6
Doğru biçimsel terim İfade İfadesi olup , bir ifade oluşturma ifadesi her iki yerde de görünebilir, bir ifadenin gerekli olduğu veya bir ifadenin gerekli olduğu yerde, bir Stringliteral sadece bir ifade bağlamında kullanılamayan bir ifadedir .
Holger

2
Kabul edilen cevap resmi olarak doğru olabilir, ancak bu daha iyi bir açıklama
edc65

3
@ edc65: bu yüzden bu cevap da olumlu oylandı. Kuralların muhakemesi ve resmi olmayan sezgisel açıklama gerçekten yardımcı olabilir, ancak her programcı, arkasında resmi kurallar olduğunun farkında olmalıdır ve resmi kuralın sonucunun sezgisel olarak anlaşılabilir olmaması durumunda, resmi kural yine de kazanacaktır. . Örneğin ()->x++yasaldır, ()->(x++)temelde aynı şeyi yapmak…
Holger

21

İlk durum sorun değil çünkü "özel" bir yöntemi (bir kurucu) çağırıyorsunuz ve yaratılan nesneyi gerçekten almıyorsunuz. Daha açık hale getirmek için, lambdalarınıza isteğe bağlı parantezleri koyacağım:

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

Ve daha açık, bunu eski gösterime çevireceğim:

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

İlk durumda bir kurucu çalıştırıyorsunuz, ancak oluşturulan nesneyi döndürmüyorsunuz, ikinci durumda bir String değeri döndürmeye çalışıyorsunuz, ancak arayüzünüzdeki yönteminiz BiConsumervoid döndürüyor, dolayısıyla derleyici hatası.


12

JLS şunu belirtir:

İşlev türünün sonucu void ise, lambda gövdesi ya bir ifade ifadesi (§14.8) ya da void uyumlu bir bloktur.

Şimdi bunu ayrıntılı olarak görelim,

Senin bu yana takeBiConsumeryöntem geçersiz tiptedir lambda alıcı new String("hi")gibi bir blok olarak yorumlayacaktır

{
    new String("hi");
}

Bu bir boşlukta geçerlidir, dolayısıyla ilk durum derlenir.

Bununla birlikte, lambda olduğu durumda -> "hi", gibi bir blok

{
    "hi";
}

java'da geçerli sözdizimi değil. Bu nedenle "merhaba" ile yapılacak tek şey denemek ve geri vermektir.

{
    return "hi";
}

bir boşlukta geçerli olmayan ve hata mesajını açıklayan

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

Daha iyi anlamak için, türünü takeBiConsumerbir String -> "hi"olarak değiştirirseniz, yalnızca dizeyi doğrudan döndürmeye çalışacağı için geçerli olacağını unutmayın.


İlk başta hatanın lambda'nın yanlış bir çağrı bağlamında olmasından kaynaklandığını düşündüğüm için bu olasılığı toplulukla paylaşacağım:

JLS 15.27

Bir programda bir atama bağlamı (§5.2), bir çağrı bağlamı (§5.3) veya bir çevrim bağlamı (§5.5) dışında bir yerde bir lambda ifadesinin ortaya çıkması derleme zamanı hatasıdır.

Ancak bizim durumumuzda, doğru olan bir çağrı bağlamındayız .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.