Biến được sử dụng trong biểu thức lambda phải là cuối cùng hoặc cuối cùng có hiệu quả


134

Biến được sử dụng trong biểu thức lambda phải là cuối cùng hoặc cuối cùng có hiệu quả

Khi tôi cố gắng sử dụng calTznó đang hiển thị lỗi này.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            VTimeZone v = (VTimeZone) component;
            v.getTimeZoneId();
            if (calTz == null) {
                calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

5
Bạn không thể sửa đổi calTztừ lambda.
Elliott Frisch

2
Tôi đã giả định rằng đây là một trong những điều chưa được thực hiện kịp thời cho Java 8. Nhưng Java 8 là 2014. Scala và Kotlin đã cho phép điều này trong nhiều năm, vì vậy rõ ràng là có thể. Có bao giờ Java có kế hoạch để loại bỏ hạn chế kỳ lạ này?
GlenPeterson

5
Đây là liên kết được cập nhật để bình luận của @MSDousti.
geisterfurz007

Tôi nghĩ bạn có thể sử dụng Tương lai hoàn thành như một cách giải quyết.
KAFain

Một điều quan trọng tôi đã quan sát - Bạn có thể sử dụng các biến tĩnh thay vì các biến thông thường (Điều này làm cho nó cuối cùng có hiệu quả tôi đoán)
kaushalpranav

Câu trả lời:


68

Một finalbiến có nghĩa là nó có thể được khởi tạo chỉ một lần. trong Java, bạn không thể sử dụng các biến không phải là cuối cùng trong lambda cũng như trong các lớp bên trong ẩn danh.

Bạn có thể cấu trúc lại mã của mình với vòng lặp for-every cũ:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    try {
        for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(calTz==null) {
               calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
           }
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

Ngay cả khi tôi không hiểu ý nghĩa của một số đoạn mã này:

  • bạn gọi a v.getTimeZoneId();mà không sử dụng giá trị trả về của nó
  • với nhiệm vụ calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());bạn không sửa đổi ban đầu được thông qua calTzvà bạn không sử dụng nó trong phương pháp này
  • Bạn luôn quay trở lại null, tại sao bạn không đặt voidloại trả về?

Hy vọng cũng những lời khuyên này sẽ giúp bạn cải thiện.


chúng ta có thể sử dụng các biến tĩnh không phải là cuối cùng
Narendra Jaggi

92

Mặc dù các câu trả lời khác chứng minh yêu cầu, họ không giải thích tại sao yêu cầu đó tồn tại.

JLS đề cập tại sao trong §15.27.2 :

Hạn chế đối với các biến cuối cùng có hiệu quả nghiêm cấm truy cập vào các biến cục bộ thay đổi động, mà việc nắm bắt có thể sẽ gây ra các vấn đề tương tranh.

Để giảm nguy cơ lỗi, họ quyết định đảm bảo các biến bị bắt không bao giờ bị đột biến.


10
Câu trả lời tốt +1, và tôi ngạc nhiên bởi mức độ bao phủ ít lý do cho trận chung kết hiệu quả dường như có được. Lưu ý: Một biến cục bộ chỉ có thể được bắt bởi lambda nếu nó cũng được gán chắc chắn trước phần thân của lambda. Cả hai yêu cầu dường như đảm bảo rằng việc truy cập vào biến cục bộ sẽ là luồng an toàn.
Tim Biegeleisen

2
bất kỳ ý tưởng tại sao điều này chỉ bị hạn chế cho các biến cục bộ, và không phải thành viên lớp? Tôi thấy mình thường xuyên phá vỡ vấn đề bằng cách tuyên bố biến của mình là một thành viên trong lớp ...
David Refaeli

4
Các thành viên của lớp @DavidRefaeli được bảo vệ / bị ảnh hưởng bởi mô hình bộ nhớ, nếu được tuân theo, sẽ tạo ra kết quả có thể dự đoán được khi chia sẻ. Các biến cục bộ thì không, như đã đề cập trong §17.4.1
Dioxin

Đây là một hack ngớ ngẩn, cần được loại bỏ. Trình biên dịch sẽ cảnh báo về khả năng truy cập biến đa luồng tiềm năng, nhưng nên cho phép nó. Hoặc, nên đủ thông minh để biết lambda của bạn có chạy trên cùng một luồng hay đang chạy song song, v.v ... Đây là một giới hạn ngớ ngẩn, khiến tôi buồn. Và như những người khác đã đề cập, các vấn đề không tồn tại trong C #.
Josh M.

@JoshM. C # cũng cho phép bạn tạo các loại giá trị có thể thay đổi mà mọi người khuyên nên tránh để ngăn chặn sự cố. Thay vì có những nguyên tắc như vậy, Java đã quyết định ngăn chặn nó hoàn toàn. Nó không làm giảm lỗi người dùng, với mức giá linh hoạt. Tôi không đồng ý với hạn chế này, nhưng nó chính đáng. Kế toán cho sự song song sẽ đòi hỏi một số công việc bổ sung vào phần cuối của trình biên dịch, đó có thể là lý do tại sao lộ trình " cảnh báo truy cập chéo " không được thực hiện. Một nhà phát triển làm việc trên thông số kỹ thuật có lẽ sẽ là xác nhận duy nhất của chúng tôi cho việc này.
Dioxin

57

Từ lambda, bạn không thể có được một tài liệu tham khảo cho bất cứ điều gì không phải là cuối cùng. Bạn cần khai báo một trình bao bọc cuối cùng từ bên ngoài lamda để giữ biến của bạn.

Tôi đã thêm đối tượng 'tham chiếu' cuối cùng làm trình bao bọc này.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    final AtomicReference<TimeZone> reference = new AtomicReference<>();

    try {
       cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(reference.get()==null) {
               reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
           }
           });
    } catch (Exception e) {
        //log.warn("Unable to determine ical timezone", e);
    }
    return reference.get();
}   

Tôi đã suy nghĩ về cách tiếp cận tương tự hoặc tương tự - nhưng tôi muốn xem một số chuyên gia tư vấn / phản hồi về câu trả lời này?
YoYo

4
Mã này bỏ lỡ một reference.set(calTz);tham chiếu ban đầu hoặc tham chiếu phải được tạo bằng cách sử dụng new AtomicReference<>(calTz), nếu không, TimeZone không null được cung cấp làm tham số sẽ bị mất.
Julien Kronegg

7
Đây phải là câu trả lời đầu tiên. Một AtomicReference (hoặc lớp Atomic___ tương tự) hoạt động xung quanh giới hạn này một cách an toàn trong mọi trường hợp có thể.
GlenPeterson

1
Đồng ý, đây sẽ là câu trả lời được chấp nhận. Các câu trả lời khác cung cấp thông tin hữu ích về cách quay lại mô hình lập trình phi chức năng và lý do tại sao điều này được thực hiện, nhưng thực tế không cho bạn biết cách khắc phục vấn đề!
Jonathan Benn

2
@GlenPeterson và cũng là một quyết định tồi tệ, không chỉ chậm hơn rất nhiều theo cách này, mà bạn còn bỏ qua các thuộc tính tác dụng phụ mà tài liệu bắt buộc.
Eugene

41

Java 8 có một khái niệm mới gọi là biến hiệu quả cuối cùng có hiệu lực. Điều đó có nghĩa là một biến cục bộ không phải là cuối cùng có giá trị không bao giờ thay đổi sau khi khởi tạo được gọi là hiệu quả cuối cùng.

Khái niệm này đã được giới thiệu bởi vì trước Java 8 , chúng tôi không thể sử dụng biến cục bộ không phải là cuối cùng trong một lớp ẩn danh . Nếu bạn muốn có quyền truy cập vào một biến cục bộ trong lớp ẩn danh , bạn phải làm cho nó cuối cùng.

Khi lambda được giới thiệu, hạn chế này đã được nới lỏng. Do đó, cần phải biến cuối cùng biến cục bộ nếu nó không thay đổi một khi nó được khởi tạo như lambda, bản thân nó không là gì ngoài một lớp ẩn danh.

Java 8 nhận ra nỗi đau của việc khai báo biến cục bộ cuối cùng mỗi khi nhà phát triển sử dụng lambda, giới thiệu khái niệm này và khiến nó không cần thiết phải biến cuối cùng biến cục bộ. Vì vậy, nếu bạn thấy quy tắc cho các lớp ẩn danh không thay đổi, chỉ là bạn không phải viết finaltừ khóa mỗi khi sử dụng lambdas.

Tôi tìm thấy một lời giải thích tốt ở đây


Định dạng mã chỉ nên được sử dụng cho , không phải cho các thuật ngữ kỹ thuật nói chung. effectively finalkhông phải là mã, đó là thuật ngữ. Xem Khi nào nên sử dụng định dạng mã cho văn bản không mã? trên Meta Stack tràn .
Charles Duffy

(Vì vậy, " finaltừ khóa" là một từ mã và chính xác để định dạng theo cách đó, nhưng khi bạn sử dụng mô tả "cuối cùng" thay vì mã, thay vào đó là thuật ngữ).
Charles Duffy

9

Trong ví dụ của bạn, bạn có thể thay thế forEachbằng lamdba bằng một forvòng lặp đơn giản và sửa đổi bất kỳ biến nào một cách tự do. Hoặc, có lẽ, cấu trúc lại mã của bạn để bạn không cần sửa đổi bất kỳ biến nào. Tuy nhiên, tôi sẽ giải thích cho đầy đủ ý nghĩa của lỗi và cách khắc phục.

Đặc tả ngôn ngữ Java 8, §15.27.2 :

Bất kỳ biến cục bộ, tham số chính thức hoặc tham số ngoại lệ nào được sử dụng nhưng không được khai báo trong biểu thức lambda phải được khai báo là cuối cùng hoặc cuối cùng có hiệu lực ( §4.12.4 ) hoặc xảy ra lỗi thời gian biên dịch khi sử dụng.

Về cơ bản, bạn không thể sửa đổi một biến cục bộ ( calTztrong trường hợp này) từ bên trong lambda (hoặc một lớp cục bộ / ẩn danh). Để đạt được điều đó trong Java, bạn phải sử dụng một đối tượng có thể thay đổi và sửa đổi nó (thông qua một biến cuối cùng) từ lambda. Một ví dụ về một đối tượng có thể thay đổi ở đây sẽ là một mảng của một phần tử:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    TimeZone[] result = { null };
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            ...
            result[0] = ...;
            ...
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return result[0];
}

Một cách khác là sử dụng một lĩnh vực của một đối tượng. Ví dụ: kết quả MyObj = new MyObj (); ...; result.timeZone = ...; ....; kết quả trả về.timezone; Lưu ý rằng, như đã giải thích ở trên, điều này làm bạn gặp phải các vấn đề về an toàn luồng. Xem stackoverflow.com/a/50341404/7092558
Gibezynu Nu

0

nếu không cần thiết phải sửa đổi biến hơn một cách giải quyết chung cho loại vấn đề này sẽ là trích xuất phần mã sử dụng lambda và sử dụng từ khóa cuối cùng trên tham số phương thức.


0

Một biến được sử dụng trong biểu thức lambda phải là một cuối cùng hoặc cuối cùng có hiệu quả, nhưng bạn có thể gán một giá trị cho một mảng phần tử cuối cùng.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        TimeZone calTzLocal[] = new TimeZone[1];
        calTzLocal[0] = calTz;
        cal.getComponents().get("VTIMEZONE").forEach(component -> {
            TimeZone v = component;
            v.getTimeZoneId();
            if (calTzLocal[0] == null) {
                calTzLocal[0] = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

Điều này rất giống với đề nghị của Alexander Udalov. Ngoài ra, tôi nghĩ rằng phương pháp này dựa vào các tác dụng phụ.
Scratte
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.