Sự khác biệt giữa cuối cùng và hiệu quả cuối cùng


350

Tôi đang chơi với lambdas trong Java 8 và tôi đã gặp cảnh báo local variables referenced from a lambda expression must be final or effectively final. Tôi biết rằng khi tôi sử dụng các biến trong lớp ẩn danh, chúng phải là cuối cùng trong lớp ngoài, nhưng - sự khác biệt giữa cuối cùngcuối cùng có hiệu quả là gì?


2
Rất nhiều câu trả lời, nhưng tất cả về cơ bản là "không có sự khác biệt." Nhưng đây có thực sự là sự thật không? Thật không may, tôi dường như không thể tìm thấy Đặc tả ngôn ngữ cho Java 8.
Alexanderr Dubinsky

3
@AleksandrDubinsky docs.oracle.com/javase/specs
eis

@AleksandrDubinsky không "thực sự" đúng. Tôi tìm thấy một ngoại lệ cho quy tắc này. Một biến cục bộ được khởi tạo với hằng số không phải là biểu thức hằng cho trình biên dịch. Bạn không thể sử dụng một biến như vậy cho một trường hợp trong một chuyển đổi / trường hợp cho đến khi bạn rõ ràng thêm từ khóa cuối cùng. Ví dụ: "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

Câu trả lời:


233

... bắt đầu trong Java SE 8, một lớp cục bộ có thể truy cập các biến và tham số cục bộ của khối kèm theo là cuối cùng hoặc cuối cùng có hiệu quả. Một biến hoặc tham số có giá trị không bao giờ thay đổi sau khi được khởi tạo là cuối cùng có hiệu lực.

Ví dụ: giả sử biến numberLengthkhông được khai báo cuối cùng và bạn thêm câu lệnh gán được đánh dấu trong hàm PhoneNumbertạo:

public class OutterClass {  

  int numberLength; // <== not *final*

  class PhoneNumber {

    PhoneNumber(String phoneNumber) {
        numberLength = 7;   // <== assignment to numberLength
        String currentNumber = phoneNumber.replaceAll(
            regularExpression, "");
        if (currentNumber.length() == numberLength)
            formattedPhoneNumber = currentNumber;
        else
            formattedPhoneNumber = null;
     }

  ...

  }

...

}

Do câu lệnh gán này, số lượng biến số không còn hiệu quả cuối cùng nữa. Kết quả là, trình biên dịch Java tạo ra một thông báo lỗi tương tự như "các biến cục bộ được tham chiếu từ một lớp bên trong phải là cuối cùng hoặc cuối cùng có hiệu quả" trong đó lớp PhoneNumber bên trong cố gắng truy cập vào biến sốLạng:

http://codeinventions.blogspot.in/2014/07/difference-b between-final-and.html

http://docs.oracle.com/javase/tutorial/java/javaOO/localgroupes.html


68
+1 Lưu ý: nếu tham chiếu không bị thay đổi thì nó hoàn toàn có hiệu lực ngay cả khi đối tượng được tham chiếu bị thay đổi.
Peter Lawrey

1
@stanleyerror Đây có thể là một số trợ giúp: stackoverflow.com/questions/4732544/iêu

1
Tôi nghĩ rằng hữu ích hơn một ví dụ về không hiệu quả cuối cùng, là một ví dụ về khi một cái gì đó cuối cùng có hiệu quả. Mặc dù mô tả không làm cho nó rõ ràng. Var không cần phải được khai báo cuối cùng nếu không có mã nào thay đổi giá trị của nó.
Skychan

1
Ví dụ không chính xác. Mã này biên dịch hoàn hảo (tất nhiên không có dấu chấm). Để nhận được lỗi trình biên dịch, mã này phải nằm trong một số phương thức để numberLengthtrở thành một biến cục bộ của phương thức này.
mykola

1
Có một lý do tại sao ví dụ này rất phức tạp? Tại sao phần lớn các mã xử lý một hoạt động regex hoàn toàn không liên quan? Và, như @mykola đã nói, nó hoàn toàn thiếu dấu hiệu liên quan đến thuộc tính cuối cùng hiệu quả , vì điều đó chỉ liên quan đến các biến cục bộ và không có biến cục bộ trong ví dụ này.
Holger

131

Tôi thấy cách đơn giản nhất để giải thích "hiệu quả cuối cùng" là tưởng tượng việc thêm công cụ finalsửa đổi vào một khai báo biến. Nếu, với sự thay đổi này, chương trình tiếp tục hành xử theo cùng một cách, cả về thời gian biên dịch và thời gian chạy, thì biến đó là cuối cùng có hiệu quả.


4
Điều này là đúng, miễn là sự hiểu biết về "bản cuối cùng" của java 8 được hiểu rõ. Mặt khác, tôi nhìn vào một biến không được khai báo cuối cùng mà bạn đã thực hiện một bài tập sau này và nhầm tưởng rằng nó không phải là cuối cùng. Bạn có thể nói "tất nhiên" ... nhưng không phải ai cũng chú ý nhiều đến phiên bản ngôn ngữ mới nhất thay đổi như họ nghĩ.
lừa4jesus

8
Một ngoại lệ cho quy tắc này là một biến cục bộ được khởi tạo với hằng số không phải là biểu thức hằng cho trình biên dịch. Bạn không thể sử dụng một biến như vậy cho một trường hợp trong một chuyển đổi / trường hợp cho đến khi bạn rõ ràng thêm từ khóa cuối cùng. Ví dụ: "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

2
Trường hợp chuyển đổi @HennoVermeulen không phải là một ngoại lệ cho quy tắc trong câu trả lời này. Ngôn ngữ chỉ định case kyêu cầu một biểu thức hằng có thể là biến không đổi ("Biến không đổi là biến cuối cùng của kiểu nguyên thủy hoặc kiểu chuỗi được khởi tạo với biểu thức không đổi" JLS 4.12.4 ) là trường hợp đặc biệt của cuối cùng Biến đổi.
Colin D Bennett

3
Trong ví dụ của tôi, trình biên dịch phàn nàn rằng k không phải là biểu thức hằng nên nó không thể được sử dụng cho công tắc. Khi thêm cuối cùng, hành vi biên dịch thay đổi bởi vì bây giờ nó là một biến không đổi và nó có thể được sử dụng trong chuyển đổi. Vì vậy, bạn đã đúng: quy tắc vẫn đúng. Nó chỉ đơn giản là không áp dụng cho ví dụ này và không nói liệu k có hiệu quả cuối cùng hay không.
Henno Vermeulen

36

Theo các tài liệu :

Một biến hoặc tham số có giá trị không bao giờ thay đổi sau khi được khởi tạo là cuối cùng có hiệu lực.

Về cơ bản, nếu trình biên dịch tìm thấy một biến không xuất hiện trong các bài tập bên ngoài khởi tạo của nó, thì biến đó được coi là hiệu quả cuối cùng .

Ví dụ, hãy xem xét một số lớp:

public class Foo {

    public void baz(int bar) {
        // While the next line is commented, bar is effectively final
        // and while it is uncommented, the assignment means it is not
        // effectively final.

        // bar = 2;
    }
}

Các tài liệu nói về các biến cục bộ. bartrong ví dụ của bạn không phải là một biến động cục bộ, mà là một trường. "Hiệu quả cuối cùng" trong thông báo lỗi như trên hoàn toàn không áp dụng cho các trường.
Antti Haapala

6
@AnttiHaapala barlà một tham số ở đây, không phải là một trường.
peter.petrov

30

'Hiệu quả cuối cùng' là một biến sẽ không gây ra lỗi trình biên dịch nếu nó được thêm vào 'cuối cùng'

Từ một bài viết của 'Brian Goetz',

Một cách không chính thức, một biến cục bộ là cuối cùng có hiệu quả nếu giá trị ban đầu của nó không bao giờ thay đổi - nói cách khác, tuyên bố nó là cuối cùng sẽ không gây ra lỗi biên dịch.

lambda-bang-chung kết- Brian Goetz


2
câu trả lời này được hiển thị dưới dạng trích dẫn, tuy nhiên không có văn bản chính xác như vậy trong bài viết của Brian, chắc chắn không phải là từ được nối thêm . Đây là một trích dẫn thay vào đó: Một cách không chính thức, một biến cục bộ là cuối cùng có hiệu quả nếu giá trị ban đầu của nó không bao giờ thay đổi - nói cách khác, tuyên bố nó cuối cùng sẽ không gây ra lỗi biên dịch.
lcfd

Từ bản sao nguyên văn bài viết: Một cách không chính thức, một biến cục bộ có hiệu lực cuối cùng nếu giá trị ban đầu của nó không bao giờ thay đổi - nói cách khác, tuyên bố nó cuối cùng sẽ không gây ra lỗi biên dịch.
Ajeet Ganga

26

Biến dưới đây là cuối cùng , vì vậy chúng tôi không thể thay đổi giá trị của nó sau khi được khởi tạo. Nếu chúng tôi cố gắng, chúng tôi sẽ gặp lỗi biên dịch ...

final int variable = 123;

Nhưng nếu chúng ta tạo một biến như thế này, chúng ta có thể thay đổi giá trị của nó ...

int variable = 123;
variable = 456;

Nhưng trong Java 8 , tất cả các biến là cuối cùng theo mặc định. Nhưng sự tồn tại của dòng thứ 2 trong mã làm cho nó không phải là cuối cùng . Vì vậy, nếu chúng tôi xóa dòng thứ 2 khỏi đoạn mã trên, biến của chúng tôi hiện là "cuối cùng có hiệu quả" ...

int variable = 123;

Vì vậy, bất kỳ biến nào được gán một lần và chỉ một lần, là "cuối cùng có hiệu quả" .


Đơn giản như câu trả lời nên được.
superigno

@Eurig, Yêu cầu trích dẫn cho "tất cả các biến là cuối cùng theo mặc định".
Pacerier

10

Một biến là cuối cùng hoặc cuối cùng có hiệu lực khi nó được khởi tạo một lần và nó không bao giờ bị biến đổi trong lớp chủ sở hữu của nó. Và chúng ta không thể khởi tạo nó trong các vòng lặp hoặc các lớp bên trong .

Chung kết :

final int number;
number = 23;

Hiệu quả cuối cùng :

int number;
number = 34;

Lưu ý : Chung kết cuối cùnghiệu quả là tương tự nhau (Giá trị của chúng không thay đổi sau khi gán) nhưng chỉ là các biến cuối cùng hiệu quả không được khai báo bằng Keyword final.


7

Khi một biểu thức lambda sử dụng một biến cục bộ được gán từ không gian kèm theo của nó, có một hạn chế quan trọng. Biểu thức lambda chỉ có thể sử dụng biến cục bộ có giá trị không thay đổi. Hạn chế đó được gọi là " bắt biến " được mô tả là; giá trị bắt biểu thức lambda, không phải biến .
Các biến cục bộ mà biểu thức lambda có thể sử dụng được gọi là " cuối cùng có hiệu quả ".
Một biến cuối cùng có hiệu quả là một biến có giá trị không thay đổi sau khi được gán lần đầu tiên. Không cần phải tuyên bố rõ ràng một biến như là cuối cùng, mặc dù làm như vậy sẽ không có lỗi.
Hãy xem ví dụ này, chúng ta có một biến cục bộ i được khởi tạo với giá trị 7, với biểu thức lambda, chúng ta đang cố gắng thay đổi giá trị đó bằng cách gán giá trị mới cho i. Điều này sẽ dẫn đến lỗi trình biên dịch - " Biến cục bộ i được xác định trong phạm vi kèm theo phải là cuối cùng hoặc cuối cùng có hiệu lực "

@FunctionalInterface
interface IFuncInt {
    int func(int num1, int num2);
    public String toString();
}

public class LambdaVarDemo {

    public static void main(String[] args){             
        int i = 7;
        IFuncInt funcInt = (num1, num2) -> {
            i = num1 + num2;
            return i;
        };
    }   
}

2

Chủ đề cuối cùng hiệu quả được mô tả trong JLS 4.12.4 và đoạn cuối bao gồm một lời giải thích rõ ràng:

Nếu một biến là cuối cùng có hiệu quả, việc thêm công cụ sửa đổi cuối cùng vào khai báo của nó sẽ không đưa ra bất kỳ lỗi thời gian biên dịch nào. Ngược lại, một biến cục bộ hoặc tham số được khai báo là cuối cùng trong một chương trình hợp lệ sẽ trở thành cuối cùng có hiệu lực nếu công cụ sửa đổi cuối cùng bị loại bỏ.


2

cuối cùng là một biến khai báo với từ khóa final, ví dụ:

final double pi = 3.14 ;

nó vẫn còn finalthông qua chương trình.

có hiệu lực cuối cùng : bất kỳ biến hoặc tham số cục bộ nào được gán một giá trị chỉ một lần ngay bây giờ (hoặc chỉ được cập nhật một lần). Nó có thể không còn hiệu quả cuối cùng thông qua chương trình. do đó, điều này có nghĩa là biến cuối cùng có hiệu lực có thể mất đi thuộc tính cuối cùng có hiệu quả của nó sau ngay khi nó được gán / cập nhật ít nhất một lần gán nữa. thí dụ:

class EffectivelyFinal {

    public static void main(String[] args) {
        calculate(124,53);
    }

    public static void calculate( int operand1, int operand2){   
     int rem = 0;  //   operand1, operand2 and rem are effectively final here
     rem = operand1%2  // rem lost its effectively final property here because it gets its second assignment 
                       // operand1, operand2 are still effectively final here 
        class operators{

            void setNum(){
                operand1 =   operand2%2;  // operand1 lost its effectively final property here because it gets its second assignment
            }

            int add(){
                return rem + operand2;  // does not compile because rem is not effectively final
            }
            int multiply(){
                return rem * operand1;  // does not compile because both rem and operand1 are not effectively final
            }
        }   
   }    
}

Điều này không đúng theo Đặc tả ngôn ngữ Java: " Bất cứ khi nào nó xuất hiện ở phía bên trái trong một biểu thức gán, nó chắc chắn không được gán và không được gán chắc chắn trước khi gán." Một biến / tham số là luôn luôn hoặc không bao giờ có hiệu quả cuối cùng. Rõ ràng hơn, nếu bạn không thể thêm finaltừ khóa vào một khai báo mà không đưa ra các lỗi biên dịch, thì nó không thực sự là cuối cùng . Đây là điểm mấu chốt của tuyên bố này: "Nếu một biến có hiệu lực cuối cùng, việc thêm công cụ sửa đổi cuối cùng vào khai báo của nó sẽ không đưa ra bất kỳ lỗi thời gian biên dịch nào."
AndrewF

Các ý kiến ​​trong mã ví dụ là không chính xác cho tất cả các lý do được mô tả trong nhận xét của tôi. "Hiệu quả cuối cùng" không phải là trạng thái có thể thay đổi theo thời gian.
AndrewF

@AndrewF nếu nó không thay đổi theo thời gian, bạn nghĩ dòng cuối cùng không biên dịch là gì? rem đã có hiệu quả cuối cùng trên dòng 1 trong phương pháp tính toán. Tuy nhiên, trên dòng cuối cùng, trình biên dịch phàn nàn rằng bản rem không hiệu quả cuối cùng
Phương pháp khoa học

Bạn đúng rằng một số mã cần phải được xóa khỏi khối mã của bạn để biên dịch, nhưng điều đó không phản ánh hành vi thời gian chạy. Tại thời điểm biên dịch, bạn có thể quyết định xem một biến có hiệu quả cuối cùng hay không - dựa trên thông số kỹ thuật, nó luôn luôn là cuối cùng một cách hiệu quả hoặc nó không bao giờ là cuối cùng một cách hiệu quả. Trình biên dịch có thể cho biết bằng cách tĩnh nhìn vào cách sử dụng biến trong phạm vi của nó. Tài sản không thể được hoặc mất khi chương trình chạy. Thuật ngữ này được xác định rõ bởi thông số kỹ thuật - kiểm tra các câu trả lời khác, giải thích nó khá tốt.
AndrewF

1
public class LambdaScopeTest {
    public int x = 0;        
    class FirstLevel {
        public int x = 1;    
        void methodInFirstLevel(int x) {

            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99; 

        }
    }    
}

Như những người khác đã nói, một biến hoặc tham số có giá trị không bao giờ thay đổi sau khi được khởi tạo là hiệu quả cuối cùng. Trong đoạn mã trên, nếu bạn thay đổi giá trị của xlớp bên trong FirstLevelthì trình biên dịch sẽ cung cấp cho bạn thông báo lỗi:

Các biến cục bộ được tham chiếu từ biểu thức lambda phải là cuối cùng hoặc cuối cùng có hiệu quả.


1

Nếu bạn có thể thêm công cụ finalsửa đổi vào một biến cục bộ, thì đó là kết quả cuối cùng.

Biểu thức Lambda có thể truy cập

  • biến tĩnh,

  • Biến thể hiện,

  • hiệu quả các tham số phương thức cuối cùng, và

  • hiệu quả các biến cục bộ cuối cùng.

Nguồn: OCP: Hướng dẫn nghiên cứu lập trình viên Java SE 8 chuyên nghiệp được chứng nhận của Oracle, Jeanne Boyarsky, Scott Selikoff

Ngoài ra,

Một effectively finalbiến là một biến có giá trị không bao giờ thay đổi, nhưng nó không được khai báo bằng finaltừ khóa.

Nguồn: Bắt đầu với Java: Từ các cấu trúc điều khiển thông qua các đối tượng (Phiên bản thứ 6), Tony Gaddis

Hơn nữa, đừng quên ý nghĩa của finalnó được khởi tạo chính xác một lần trước khi nó được sử dụng lần đầu tiên.


0

Khai báo một biến finalhoặc không khai báo nó final, nhưng giữ cho nó có hiệu quả cuối cùng có thể dẫn đến (phụ thuộc vào trình biên dịch) trong mã byte khác nhau.

Chúng ta hãy xem một ví dụ nhỏ:

    public static void main(String[] args) {
        final boolean i = true;   // 6  // final by declaration
        boolean j = true;         // 7  // effectively final

        if (i) {                  // 9
            System.out.println(i);// 10
        }
        if (!i) {                 // 12
            System.out.println(i);// 13
        }
        if (j) {                  // 15
            System.out.println(j);// 16
        }
        if (!j) {                 // 18
            System.out.println(j);// 19
        }
    }

Mã byte tương ứng của mainphương thức (Java 8u161 trên Windows 64 Bit):

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_1
       3: istore_2
       4: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       7: iconst_1
       8: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      11: iload_2
      12: ifeq          22
      15: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      18: iload_2
      19: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      22: iload_2
      23: ifne          33
      26: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      29: iload_2
      30: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      33: return

Bảng số dòng tương ứng:

 LineNumberTable:
   line 6: 0
   line 7: 2
   line 10: 4
   line 15: 11
   line 16: 15
   line 18: 22
   line 19: 26
   line 21: 33

Như chúng ta thấy mã nguồn tại dòng 12, 13, 14không xuất hiện trong các mã byte. Đó là bởi vì itruevà sẽ không thay đổi trạng thái của nó. Do đó, mã này là không thể truy cập (nhiều hơn trong câu trả lời này ). Đối với cùng một lý do mã ở dòng 9bỏ lỡ quá. Tình trạng ikhông phải được đánh giá vì nó là truechắc chắn.

Mặt khác mặc dù biến jmột cách hiệu quả thức nó không được xử lý theo cách tương tự. Không có tối ưu hóa như vậy được áp dụng. Trạng thái của jđược đánh giá hai lần. Mã byte là như nhau bất kể jcuối cùng có hiệu quả .


Tôi sẽ coi đây là một trình biên dịch không hiệu quả, và không nhất thiết là trình biên dịch vẫn đúng trong các trình biên dịch mới hơn. Trong một biên dịch hoàn hảo, nếu một biến có hiệu quả cuối cùng thì nó sẽ tạo ra tất cả các tối ưu hóa chính xác giống như một biến cuối cùng được khai báo. Vì vậy, đừng dựa vào khái niệm rằng hiệu quả cuối cùng sẽ tự động chậm hơn so với tuyên bố một cái gì đó cuối cùng.
AndrewF

@AndrewF Nói chung bạn đúng, hành vi có thể thay đổi. Đó là lý do tại sao tôi viết " có thể dẫn đến (phụ thuộc vào trình biên dịch) trong mã byte khác nhau ". Chỉ vì tối ưu hóa bị thiếu (mã byte khác nhau) tôi sẽ không cho rằng việc thực thi chậm hơn. Nhưng nó vẫn là một sự khác biệt trong trường hợp được hiển thị.
LuCio

0

Biến hiệu quả cuối cùng là một biến cục bộ là:

  1. Không được định nghĩa là final
  2. Được chỉ định một lần.

Trong khi một biến cuối cùng là một biến đó là:

  1. tuyên bố với một finaltừ khóa.

-6

Tuy nhiên, bắt đầu trong Java SE 8, một lớp cục bộ có thể truy cập các biến và tham số cục bộ của khối> bao quanh là cuối cùng hoặc cuối cùng có hiệu quả.

Điều này đã không bắt đầu trên Java 8, tôi sử dụng nó từ lâu. Mã này được sử dụng (trước java 8) là hợp pháp:

String str = ""; //<-- not accesible from anonymous classes implementation
final String strFin = ""; //<-- accesible 
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
         String ann = str; // <---- error, must be final (IDE's gives the hint);
         String ann = strFin; // <---- legal;
         String str = "legal statement on java 7,"
                +"Java 8 doesn't allow this, it thinks that I'm trying to use the str declared before the anonymous impl."; 
         //we are forced to use another name than str
    }
);

2
Câu lệnh đề cập đến thực tế là trong <Java 8, chỉ final có thể truy cập các biến, nhưng trong Java 8 cũng có các biến cuối cùng có hiệu quả .
Antti Haapala

Tôi chỉ thấy mã không hoạt động, bất kể bạn đang sử dụng Java 7 hay Java 8.
Holger
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.