2015-03-25 27 views
81

Mã Java sau thất bại trong việc biên dịch:Tại sao Java 8 lambda này 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 
} 

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

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

Điều 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" thất bại . Họ có vẻ giống hệt nhau.

+5

đây có phải là lỗi đánh máy ở đây mà phương thức giao diện chức năng trả về void không? –

+6

@NathanHughes Không. Nó trở thành trung tâm của câu hỏi - xem câu trả lời được chấp nhận. –

+0

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

Trả lời

98

Lambda của bạn cần phải đồng dư với BiConsumer<String, String>. Nếu bạn tham khảo JLS #15.27.3 (Type of a Lambda):

Một biểu thức lambda là đồng dư với một loại chức năng nếu tất cả những điều sau đây là đúng:

  • [...]
  • Nếu kết quả loại chức năng là khoảng trống , cơ thể 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 khoảng trống.

Vì vậy, các lambda phải hoặc là một biểu thức tuyên bố hoặc một khoảng trống khối tương thích:

  • Một invocation constructor được a statement expression nên nó biên dịch.
  • Chuỗi ký tự không phải là biểu thức tuyên bố và không bị vô hiệu tương thích (xem the examples in 15.27.2) để nó không biên dịch.
+30

@BrianGordon Một chuỗi chữ là một biểu thức (một biểu thức liên tục chính xác) nhưng không phải là biểu thức câu lệnh. – assylias

11

Các JLS xác định rằng

Nếu kết quả loại chức năng là bãi bỏ, cơ thể lambda hoặc là một biểu statement (§14.8) hoặc một khối trống tương thích.

Bây giờ chúng ta hãy xem điều đó một cách chi tiết,

Kể từ khi phương pháp takeBiConsumer của bạn là kiểu void, lambda nhận new String("hi") sẽ giải thích nó như là một khối như

{ 
    new String("hi"); 
} 

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

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

{ 
    "hi"; 
} 

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

{ 
    return "hi"; 
} 

mà không có giá trị trong một khoảng trống và giải thích các thông báo lỗi

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

Đối với một sự hiểu biết tốt hơn, lưu ý rằng nếu bạn thay đổi kiểu của takeBiConsumer vào một String, -> "hi" sẽ có hiệu lực vì nó chỉ đơn giản là cố gắng trả lại trực tiếp chuỗi.


Lưu ý rằng lúc đầu tôi tought lỗi là do lambda là trong bối 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

Nó là một lỗi biên dịch thời gian nếu một biểu thức lambda xảy ra trong một chương trình ở một nơi khác với ngữ cảnh gán (§5.2), một mệnh đề ngữ cảnh (§5.3), hoặc ngữ cảnh đúc (§5.5).

Tuy nhiên trong trường hợp của chúng tôi, chúng tôi đang ở một số invocation context đúng.

21

Trường hợp đầu tiên là ok vì bạn đang gọi phương thức "đặc biệt" (hàm tạo) và bạn không thực sự lấy đối tượng đã tạo.Chỉ cần để làm cho nó rõ ràng hơn, tôi sẽ đặt dấu ngoặc tùy chọn trong 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 đó để ký hiệu cũ:

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 hiện một hàm 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ố trả về một giá trị String, nhưng phương thức của bạn trong giao diện BiConsumer trả về void, do đó lỗi trình biên dịch.

42

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

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

+6

Cụm từ chính thức chính xác là [* Biểu thức biểu thức *] (http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.8), một biểu thức tạo thể hiện có thể xuất hiện ở cả hai vị trí, trong đó một biểu thức hoặc nơi một câu lệnh được yêu cầu, trong khi một 'Chuỗi' chỉ là một biểu thức * * mà không thể được sử dụng trong ngữ cảnh * statement *. – Holger

+1

Câu trả lời được chấp nhận có thể chính xác, nhưng câu trả lời này là 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 tăng lên. Tuy nhiên, lý do cho các quy tắc và giải thích trực quan không chính thức có thể giúp ích, mọi lập trình viên nên lưu ý 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 là * không * trực quan dễ hiểu, quy tắc chính thức sẽ vẫn thắng. Ví dụ. '() -> x ++' là hợp pháp, trong khi '() -> (x ++)', về cơ bản làm chính xác như nhau, không phải là ... – Holger

Các vấn đề liên quan