เหตุใด Java 8 lambda นี้จึงไม่สามารถคอมไพล์ได้


85

คอมไพล์โค้ด Java ต่อไปนี้ไม่สำเร็จ:

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

คอมไพเลอร์รายงาน:

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

สิ่งที่แปลกคือบรรทัดที่ระบุว่า "ตกลง" คอมไพล์ได้ดี แต่บรรทัดที่ระบุว่า "Error" ล้มเหลว ดูเหมือนว่าจะเหมือนกันเป็นหลัก


5
มันพิมพ์ผิดหรือเปล่าที่เมธอดอินเทอร์เฟซการทำงานส่งคืนโมฆะ
Nathan Hughes

6
@NathanHughes Nope. กลายเป็นศูนย์กลางของคำถาม - ดูคำตอบที่ยอมรับ
Brian Gordon

ควรมีรหัสอยู่ใน{ }ของtakeBiConsumer... และถ้าเป็นเช่นนั้นคุณช่วยยกตัวอย่างได้ไหม ... ถ้าฉันอ่านถูกต้องbcเป็นตัวอย่างของคลาส / อินเทอร์เฟซBiConsumerดังนั้นจึงควรมีวิธีการที่เรียกว่าacceptเพื่อให้ตรงกับลายเซ็นของอินเทอร์เฟซ .. ... และถ้าถูกต้องacceptเมธอดนั้นจะต้องถูกกำหนดไว้ที่ใดที่หนึ่ง (เช่นคลาสที่ใช้อินเทอร์เฟซ) ... ดังนั้นสิ่งที่ควรอยู่ใน{}?? ... ... ... ขอบคุณ
dsdsdsdsd

อินเทอร์เฟซด้วยวิธีการเดียวสามารถใช้แทนกันได้กับ lambdas ใน Java 8 ในกรณี(String s1, String s2) -> "hi"นี้เป็นอินสแตนซ์ของ BiConsumer <String, String>
Brian Gordon

คำตอบ:


100

BiConsumer<String, String>แลมบ์ดาของคุณจะต้องสอดคล้องกับ หากคุณอ้างถึงJLS # 15.27.3 (Type of a Lambda) :

นิพจน์แลมบ์ดาสอดคล้องกับประเภทฟังก์ชันหากทั้งหมดต่อไปนี้เป็นจริง:

  • [... ]
  • หากผลลัพธ์ของชนิดฟังก์ชันเป็นโมฆะเนื้อแลมด้าจะเป็นนิพจน์คำสั่ง (§14.8) หรือบล็อกที่เข้ากันได้กับโมฆะ

ดังนั้นแลมบ์ดาต้องเป็นนิพจน์คำสั่งหรือบล็อกที่เข้ากันได้กับโมฆะ:


31
@BrianGordon สตริงลิเทอรัลคือนิพจน์ (นิพจน์คงที่ต้องแม่นยำ) แต่ไม่ใช่นิพจน์คำสั่ง
assylias

44

โดยพื้นฐานแล้วnew String("hi")เป็นโค้ดที่เรียกใช้งานได้ซึ่งทำอะไรบางอย่างได้จริง (สร้างสตริงใหม่แล้วส่งคืน) ค่าที่ส่งคืนสามารถถูกละเว้นและnew String("hi")ยังสามารถใช้ใน void-return lambda เพื่อสร้าง String ใหม่

อย่างไรก็ตาม"hi"เป็นเพียงค่าคงที่ที่ไม่ได้ทำอะไรเลย สิ่งเดียวที่เหมาะสมที่จะทำอย่างไรกับมันในร่างกายแลมบ์ดาคือการกลับมามัน แต่วิธีแลมบ์ดาจะต้องมีชนิดการส่งคืนStringหรือObjectแต่จะส่งกลับvoidดังนั้นString cannot be casted to voidข้อผิดพลาด


6
คำที่เป็นทางการที่ถูกต้องคือExpression Statement นิพจน์การสร้างอินสแตนซ์อาจปรากฏขึ้นที่ทั้งสองตำแหน่งโดยที่นิพจน์หรือตำแหน่งที่จำเป็นต้องใช้คำสั่งในขณะที่ลิเทอStringรัลเป็นเพียงนิพจน์ที่ไม่สามารถใช้ในบริบทคำสั่งได้
Holger

2
คำตอบที่ยอมรับอาจถูกต้องอย่างเป็นทางการ แต่คำตอบนี้เป็นคำอธิบายที่ดีกว่า
edc65

3
@ edc65: นั่นคือเหตุผลที่คำตอบนี้ได้รับการโหวตเช่นกัน การให้เหตุผลสำหรับกฎและคำอธิบายที่เข้าใจง่ายที่ไม่เป็นทางการอาจช่วยได้อย่างแท้จริงอย่างไรก็ตามโปรแกรมเมอร์ทุกคนควรตระหนักว่ามีกฎที่เป็นทางการอยู่เบื้องหลังและในกรณีที่ผลลัพธ์ของกฎอย่างเป็นทางการไม่สามารถเข้าใจได้โดยสังหรณ์ใจกฎที่เป็นทางการจะยังคงชนะอยู่ . เช่น()->x++ถูกกฎหมายในขณะที่()->(x++)ทำเหมือนกันทุก
ประการ

21

กรณีแรกใช้ได้เพราะคุณกำลังเรียกใช้เมธอด "พิเศษ" (ตัวสร้าง) และคุณไม่ได้ใช้วัตถุที่สร้างขึ้นจริง เพื่อให้ชัดเจนยิ่งขึ้นฉันจะใส่วงเล็บเสริมใน lambdas ของคุณ:

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

และชัดเจนยิ่งขึ้นฉันจะแปลสิ่งนั้นเป็นสัญกรณ์ที่เก่ากว่า:

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

ในกรณีแรกคุณกำลังเรียกใช้คอนสตรัคเตอร์ แต่คุณไม่ได้ส่งคืนอ็อบเจ็กต์ที่สร้างขึ้นในกรณีที่สองคุณกำลังพยายามส่งคืนค่าสตริง แต่เมธอดของคุณในอินเทอร์เฟซของคุณBiConsumerจะคืนค่าเป็นโมฆะดังนั้นข้อผิดพลาดของคอมไพเลอร์


12

JLS ระบุว่า

หากผลลัพธ์ของชนิดฟังก์ชันเป็นโมฆะเนื้อแลมด้าจะเป็นนิพจน์คำสั่ง (§14.8) หรือบล็อกที่เข้ากันได้กับโมฆะ

ตอนนี้เรามาดูรายละเอียดกัน

เนื่องจากtakeBiConsumerวิธีการของคุณเป็นประเภทโมฆะแลมบ์ดาที่ได้รับnew String("hi")จะตีความว่าเป็นบล็อกเหมือน

{
    new String("hi");
}

ซึ่งถูกต้องในโมฆะดังนั้นกรณีแรกคอมไพล์

อย่างไรก็ตามในกรณีที่แลมบ์ดาอยู่-> "hi"บล็อกเช่น

{
    "hi";
}

ไม่ใช่ไวยากรณ์ที่ถูกต้องใน java ดังนั้นสิ่งเดียวที่ต้องทำกับ "สวัสดี" คือการพยายามส่งคืน

{
    return "hi";
}

ซึ่งไม่ถูกต้องในโมฆะและอธิบายข้อความแสดงข้อผิดพลาด

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

เพื่อความเข้าใจที่ดีขึ้นโปรดทราบว่าหากคุณเปลี่ยนประเภทของtakeBiConsumerเป็นสตริง-> "hi"จะใช้ได้เนื่องจากจะพยายามส่งคืนสตริงโดยตรง


โปรดทราบว่าในตอนแรกฉันคิดว่าข้อผิดพลาดเกิดจากแลมบ์ดาอยู่ในบริบทการร้องขอที่ไม่ถูกต้องดังนั้นฉันจะแบ่งปันความเป็นไปได้นี้กับชุมชน:

JLS 15.27

เป็นข้อผิดพลาดเวลาคอมไพล์หากนิพจน์แลมบ์ดาเกิดขึ้นในโปรแกรมในบางที่นอกเหนือจากบริบทการกำหนด (§5.2) บริบทการเรียกใช้ (§5.3) หรือบริบทการแคสต์ (§5.5)

อย่างไรก็ตามในกรณีของเราเราอยู่ในบริบทการร้องขอซึ่งถูกต้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.