Java 8 lambda biến có thể bắt biến từ tham số phương thức?


8

Tôi đang sử dụng AdoptOpenJDK jdk81212-b04trên Ubuntu Linux, chạy trên Eclipse 4.13. Tôi có một phương pháp trong Swing tạo ra lambda bên trong lambda; cả hai có thể được gọi trên các chủ đề riêng biệt. Nó trông như thế này (mã giả):

private SwingAction createAction(final Data payload) {
  System.out.println(System.identityHashCode(payload));
  return new SwingAction(() -> {
    System.out.println(System.identityHashCode(payload));
    //do stuff
    //show an "are you sure?" modal dialog and get a response
    //show a file selection dialog
    //when the dialog completes, create a worker and show a status:
    showStatusDialogWithWorker(() -> new SwingWorker() {
      protected String doInBackground() {
        save(payload);
      }
    });

Bạn có thể thấy rằng lambdas sâu vài lớp và cuối cùng "tải trọng" được chụp sẽ được lưu vào một tệp, ít nhiều.

Nhưng trước khi xem xét các lớp và các chủ đề, chúng ta hãy đi thẳng vào vấn đề:

  1. Lần đầu tiên tôi gọi createAction(), hai System.out.println()phương thức in mã băm chính xác giống nhau, chỉ ra rằng tải trọng bị bắt bên trong lambda giống như tôi đã chuyển qua createAction().
  2. Nếu sau này tôi gọi createAction()với một trọng tải khác nhau, hai System.out.println()giá trị được in sẽ khác nhau! Cụ thể, dòng thứ hai được in luôn chỉ ra cùng một giá trị đã được in ở bước 1 !!

Tôi có thể lặp lại điều này nhiều lần; tải trọng thực tế được thông qua sẽ tiếp tục nhận được mã băm nhận dạng khác nhau, trong khi dòng thứ hai được in (bên trong lambda) sẽ giữ nguyên! Cuối cùng, một cái gì đó sẽ nhấp và đột nhiên các số sẽ giống nhau một lần nữa, nhưng sau đó chúng sẽ phân kỳ với cùng một hành vi.

Có phải Java bằng cách nào đó lưu trữ lambda, cũng như đối số sẽ xảy ra với lambda? Nhưng làm thế nào là điều này có thể? Cuộc payloadtranh cãi đã được đánh dấu final, và bên cạnh đó, việc bắt giữ lambda dù sao cũng phải là trận chung kết hiệu quả!

  • Có một lỗi Java 8 không nhận ra lambda không nên được lưu vào bộ đệm nếu biến bị bắt là một số lambdas sâu?
  • Có một lỗi Java 8 đang lưu trữ các đối số lambdas và lambda trên các luồng không?
  • Hoặc có điều gì đó mà tôi không hiểu về lambda đối số phương thức so với biến cục bộ của phương thức?

Lần thử đầu tiên không thành công

Hôm qua tôi nghĩ rằng tôi có thể ngăn chặn hành vi này chỉ bằng cách chụp tham số phương thức cục bộ trên ngăn xếp phương thức:

private SwingAction createAction(final Data payload) {
  final Data theRealPayload = payload;
  System.out.println(System.identityHashCode(theRealPayload));
  return new SwingAction(() -> {
    System.out.println(System.identityHashCode(theRealPayload));
    //do stuff
    //show an "are you sure?" modal dialog and get a response
    //show a file selection dialog
    //when the dialog completes, create a worker and show a status:
    showStatusDialogWithWorker(() -> new SwingWorker() {
      protected String doInBackground() {
        save(theRealPayload);
      }
    });

Với dòng đơn đó Data theRealPayload = payload, nếu tôi sử dụng theRealPayloadthay vì payloadđột nhiên thì lỗi không còn xuất hiện nữa và mỗi lần tôi gọi createAction(), hai dòng in chỉ ra chính xác cùng một thể hiện của biến bị bắt.

Tuy nhiên hôm nay cách giải quyết này đã ngừng hoạt động.

Vấn đề địa chỉ sửa lỗi riêng biệt; Nhưng tại sao?

Tôi tìm thấy một lỗi riêng biệt đang ném một ngoại lệ vào bên trong showStatusDialogWithWorker(). Về cơ bản showStatusDialogWithWorker()được cho là tạo worker (trong lambda đã qua) và hiển thị hộp thoại trạng thái cho đến khi worker hoàn thành. Có một lỗi sẽ tạo ra worker chính xác, nhưng không tạo được hộp thoại, ném ra một ngoại lệ sẽ nổi bong bóng và không bao giờ bị bắt. Tôi đã sửa lỗi này để showStatusDialogWithWorker()hộp thoại hiển thị thành công khi worker đang chạy và sau đó đóng nó sau khi worker hoàn thành. Bây giờ tôi không còn có thể tái tạo vấn đề chụp lambda nữa.

Nhưng tại sao một cái gì đó bên trong showStatusDialogWithWorker()liên quan đến vấn đề? Khi tôi đang in ra System.identityHashCode()bên ngoài và bên trong lambda, và các giá trị khác nhau, điều này đã xảy ra trước khi showStatusDialogWithWorker() được gọi và trước khi ngoại lệ được ném ra. Tại sao một ngoại lệ sau này làm cho một sự khác biệt?

Bên cạnh đó, câu hỏi cơ bản vẫn là: làm thế nào thậm chí có thể một finaltham số được truyền bởi một phương thức và được lambda bắt giữ có thể thay đổi?


5
Tôi không chắc đó có phải là một ví dụ hoàn chỉnh không . Tôi rất thích dán mã và tự kiểm tra nó, nhưng với những gì bạn đã chia sẻ, tôi không thể. Bạn có thể cung cấp ví dụ tái sản xuất tối thiểu ?
Fureeish

Tôi không thể cung cấp cho bạn một ví dụ có thể lặp lại tối thiểu tại thời điểm này, nhưng mã trong ví dụ này đủ để thảo luận về lý thuyết. Từ quan điểm lý thuyết, làm thế nào danh tính của một finalđối số phương thức có thể thay đổi bên ngoài và bên trong lambda? Và tại sao việc bắt biến là một finalbiến cục bộ trên ngăn xếp sẽ tạo ra sự khác biệt?
Garret Wilson

Ngày nay, việc nắm bắt biến bên trong phương thức là một finalbiến trên ngăn xếp không còn khắc phục được vấn đề. Tôi tiếp tục điều tra.
Garret Wilson

1
Với trường hợp sử dụng của bạn, điều duy nhất nảy ra trong đầu bạn là tuần tự hóa lambda; Điều đó sẽ giải thích tại sao sự thay đổi tham chiếu. Cũng là tải trọng bằng? Nó có cùng nội dung hơn trước không?
NoDataFound

1
Tôi có một nghi ngờ lén lút rằng lỗi ở đây không phải đến từ việc bắt lambda, mà là từ SwingActionchính nó. Bạn làm gì với SwingActiontrả về từ phương thức?
Leo Aso

Câu trả lời:


2

Làm thế nào thậm chí có thể một tham số cuối cùng được truyền bởi một phương thức và được lambda bắt giữ có thể thay đổi?

Không phải vậy. Như bạn đã chỉ ra, trừ khi có lỗi trong JVM, điều này không thể xảy ra.

Điều này là rất khó để xác định mà không có một ví dụ tái sản xuất tối thiểu. Dưới đây là những quan sát bạn đã thực hiện:

  1. Lần đầu tiên tôi gọi createAction (), hai phương thức System.out.println () in mã băm chính xác giống nhau, chỉ ra rằng tải trọng được bắt giữ bên trong lambda giống như tôi đã truyền cho createdAction ().
  2. Nếu sau này tôi gọi createdAction () với một tải trọng khác, hai giá trị System.out.println () được in sẽ khác nhau! Cụ thể, dòng thứ hai được in luôn chỉ ra cùng một giá trị đã được in ở bước 1 !!

Một lời giải thích có thể phù hợp với bằng chứng là lambda được gọi lần thứ hai trên thực tế là lambda từ lần chạy đầu tiên, và lambda được tạo ra bởi lần chạy thứ hai đã bị loại bỏ. Điều đó sẽ đưa ra các quan sát trên và sẽ đặt lỗi bên trong mã mà bạn chưa hiển thị ở đây.

Có lẽ bạn có thể thêm một số ghi nhật ký bổ sung để ghi lại: a) id của bất kỳ lambdas nào được tạo bên trong createdAction tại thời điểm tạo (Tôi nghĩ bạn sẽ cần thay đổi lambdas thành các lớp anon thực hiện giao diện gọi lại bằng cách đăng nhập vào hàm tạo của chúng) b) ids của lambdas tại thời điểm cầu nguyện của họ

Tôi nghĩ rằng việc ghi nhật ký trên sẽ đủ để chứng minh hoặc bác bỏ lý thuyết của tôi.

GL!


0

Tôi không tìm thấy bất cứ điều gì sai với lambdas bị bắt trong mã của bạn.

Cách giải quyết của bạn không thay đổi việc chụp cục bộ khi bạn vừa khai báo một biến mới được gán cho cùng một tham chiếu.

Vấn đề rất có thể là việc bạn xử lý SwingActionđối tượng đã tạo. Tôi sẽ không ngạc nhiên nếu bạn thấy rằng việc in IdentityHashCode của đối tượng được trả về nơi bạn đang sử dụng sẽ mang lại các giá trị phù hợp với tải trọng của bạn. Hay nói cách khác, bạn có thể đang sử dụng một tài liệu tham khảo trước đó SwingAction.

Bên cạnh đó, câu hỏi cơ bản vẫn là: làm thế nào thậm chí có thể một tham số cuối cùng được truyền bởi một phương thức và được bắt bởi lambda có thể thay đổi?

Điều này không nên có thể ở mức tham chiếu, biến không thể được chỉ định lại. Tham chiếu được thông qua có thể là chính nó có thể thay đổi, nhưng điều đó không áp dụng cho trường hợp này.

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.