Tại sao Java 8 lambda này không biên dịch được?


85

Mã Java sau không biên dịch được:

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

Trình biên dịch báo cáo:

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

Điều kỳ lạ là dòng được đánh dấu "OK" biên dịch tốt, nhưng dòng được đánh dấu "Lỗi" không thành công. Về cơ bản chúng có vẻ giống hệt nhau.


5
Đây có phải là lỗi đánh máy mà phương thức giao diện chức năng trả về void?
Nathan Hughes

6
@NathanHughes Không. Hóa ra nó là trọng tâm của câu hỏi- hãy xem câu trả lời được chấp nhận.
Brian Gordon

liệu có mã bên trong { }của takeBiConsumer... và nếu có, bạn có thể cho một ví dụ ... nếu tôi đọc đúng, bclà một thể hiện của lớp / giao diện BiConsumervà do đó phải chứa một phương thức được gọi acceptđể khớp với chữ ký giao diện. .. ... và nếu điều đó đúng, thì acceptphương thức cần phải được xác định ở đâu đó (ví dụ như một lớp thực thi giao diện) ... vậy đó có phải là những gì nên có trong {}?? ... ... ... cảm ơn
dsdsdsdsd

Các giao diện với một phương thức duy nhất có thể hoán đổi cho nhau bằng lambdas trong Java 8. Trong trường hợp này, (String s1, String s2) -> "hi"là một thể hiện của BiConsumer <String, String>.
Brian Gordon,

Câu trả lời:


100

Lambda của bạn cần phải phù hợp với BiConsumer<String, String>. Nếu bạn tham khảo JLS # 15.27.3 (Loại Lambda) :

Một biểu thức lambda là đồng dư với một kiểu hàm nếu tất cả các điều sau đây đều đúng:

  • [...]
  • Nếu kết quả của kiểu hàm là void, thì phần thân lambda là một biểu thức câu lệnh (§14.8) hoặc một khối tương thích với void.

Vì vậy, lambda phải là một biểu thức câu lệnh hoặc một khối tương thích void:


31
@BrianGordon Một chuỗi ký tự là một biểu thức (chính xác là một biểu thức hằng số) nhưng không phải là một biểu thức câu lệnh.
assylias

44

Về cơ bản, new String("hi")là một đoạn mã thực thi thực sự làm một việc gì đó (nó tạo một Chuỗi mới và sau đó trả về nó). Giá trị trả về có thể bị bỏ qua và new String("hi")vẫn có thể được sử dụng trong void-return lambda để tạo một Chuỗi mới.

Tuy nhiên, "hi"chỉ là một hằng số không tự làm bất cứ điều gì. Điều hợp lý duy nhất để làm với nó trong lambda body là trả lại nó. Nhưng phương thức lambda sẽ phải có kiểu trả về Stringhoặc Object, nhưng nó trả về void, do đó có String cannot be casted to voidlỗi.


6
Thuật ngữ chính thức chính xác là Câu lệnh biểu thức , một biểu thức tạo thể hiện có thể xuất hiện ở cả hai nơi, nơi biểu thức hoặc nơi yêu cầu một câu lệnh, trong khi một Stringnghĩa đen chỉ là một biểu thức không thể được sử dụng trong ngữ cảnh câu lệnh .
Holger

2
Câu trả lời được chấp nhận có thể chính thức đúng, nhưng điều này là một lời giải thích tốt hơn
edc65

3
@ edc65: đó là lý do tại sao câu trả lời này cũng được ủng hộ. Việc lập luận cho các quy tắc và giải thích trực quan không chính thức có thể thực sự hữu ích, tuy nhiên, mọi lập trình viên nên biết rằng có các quy tắc chính thức đằng sau nó và trong trường hợp kết quả của quy tắc chính thức không thể hiểu được bằng trực giác, quy tắc chính thức vẫn sẽ thắng . Ví dụ: ()->x++là hợp pháp, trong khi ()->(x++), về cơ bản làm hoàn toàn giống nhau, thì không…
Holger

21

Trường hợp đầu tiên là ok vì bạn đang gọi một phương thức "đặc biệt" (một hàm tạo) và bạn không thực sự lấy đối tượng đã tạo. Để làm rõ hơn, tôi sẽ đặt các dấu ngoặc nhọn tùy chọn vào lambdas của bạn:

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

Và rõ ràng hơn, tôi sẽ dịch nó sang ký hiệu cũ hơn:

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

Trong trường hợp đầu tiên, bạn đang thực thi một phương thức khởi tạo, nhưng bạn KHÔNG trả về đối tượng đã tạo, trong trường hợp thứ hai, bạn đang cố gắng trả về giá trị Chuỗi, nhưng phương thức của bạn trong giao diện của bạn BiConsumertrả về giá trị void, do đó lỗi trình biên dịch.


12

JLS chỉ định rằng

Nếu kết quả của kiểu hàm là void, thì phần thân lambda là một biểu thức câu lệnh (§14.8) hoặc một khối tương thích với void.

Bây giờ hãy xem chi tiết điều đó,

takeBiConsumerphương thức của bạn thuộc loại void nên phương thức nhận lambda new String("hi")sẽ diễn giải nó thành một khối như

{
    new String("hi");
}

mà là hợp lệ trong một khoảng trống, do đó trường hợp đầu tiên biên dịch.

Tuy nhiên, trong trường hợp lambda là -> "hi", một khối chẳng hạn như

{
    "hi";
}

không phải là cú pháp hợp lệ trong java. Do đó, điều duy nhất cần làm với "hi" là thử và gửi lại.

{
    return "hi";
}

không hợp lệ và giải thích thông báo lỗi

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

Để hiểu rõ hơn, hãy lưu ý rằng nếu bạn thay đổi kiểu takeBiConsumerthành Chuỗi, -> "hi"sẽ hợp lệ vì nó sẽ chỉ cố gắng trực tiếp trả về chuỗi.


Lưu ý rằng lúc đầu, tôi nghĩ rằng lỗi là do lambda ở trong ngữ cảnh gọi sai, vì vậy tôi sẽ chia sẻ khả năng này với cộng đồng:

JLS 15,27

Đó là lỗi thời gian biên dịch nếu biểu thức lambda xảy ra trong một chương trình ở nơi nào đó không phải là ngữ cảnh gán (§5.2), ngữ cảnh gọi (§5.3) hoặc bối cảnh ép kiểu (§5.5).

Tuy nhiên trong trường hợp của chúng tôi, chúng tôi đang ở trong một ngữ cảnh gọi là chính xác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.