Các biến Java Final có giá trị mặc định không?


81

Tôi có một chương trình như thế này:

class Test {

    final int x;

    {
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }

}

Nếu tôi cố gắng thực thi nó, tôi gặp lỗi trình biên dịch như: variable x might not have been initializeddựa trên các giá trị mặc định của java, tôi sẽ nhận được đầu ra bên dưới phải không ??

"Here x is 0".

Các biến cuối cùng có giá trị mặc định không?

nếu tôi thay đổi mã của mình như thế này,

class Test {

    final int x;

    {
        printX();
        x = 7;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }

}

Tôi nhận được đầu ra là:

Here x is 0                                                                                      
Here x is 7                                                                                     
const called

Bất cứ ai có thể vui lòng giải thích hành vi này ..

Câu trả lời:


62

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html , chương "Khởi tạo thành viên phiên bản":

Trình biên dịch Java sao chép các khối khởi tạo vào mọi phương thức khởi tạo.

Điều đó có nghĩa là:

{
    printX();
}

Test() {
    System.out.println("const called");
}

hành xử chính xác như:

Test() {
    printX();
    System.out.println("const called");
}

Như bạn có thể thấy, khi một phiên bản đã được tạo, trường cuối cùng chắc chắn chưa được chỉ định , trong khi (từ http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html # jls-8.3.1.2 ):

Một biến cá thể cuối cùng trống chắc chắn phải được gán ở cuối mỗi hàm tạo của lớp mà nó được khai báo; nếu không xảy ra lỗi thời gian biên dịch.

Mặc dù nó dường như không được tuyên bố rõ ràng trong tài liệu (ít nhất là tôi không thể tìm thấy nó), trường cuối cùng phải tạm thời lấy giá trị mặc định của nó trước khi kết thúc hàm tạo, để nó có giá trị có thể dự đoán được nếu bạn đọc nó trước khi chuyển nhượng.

Giá trị mặc định: http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5

Trên đoạn mã thứ hai của bạn, x được khởi tạo khi tạo phiên bản, vì vậy trình biên dịch không phàn nàn:

Test() {
    printX();
    x = 7;
    printX();
    System.out.println("const called");
}

Cũng lưu ý rằng cách tiếp cận sau đây không hoạt động. Sử dụng giá trị mặc định của biến cuối cùng chỉ được phép thông qua một phương thức.

Test() {
    System.out.println("Here x is " + x); // Compile time error : variable 'x' might not be initialized
    x = 7;
    System.out.println("Here x is " + x);
    System.out.println("const called");
}

1
Có thể cần lưu ý nơi mà lệnh gọi ngầm (hoặc rõ ràng) tới super () đi vào một trong các ví dụ của bạn.
Patrick

2
Điều này không trả lời tại sao không khởi tạo trường cuối cùng gây ra lỗi biên dịch.
justhalf,

@ sp00m Tham khảo tốt - Tôi sẽ đưa nó vào ngân hàng.
Bohemian

2
@justhalf câu trả lời thiếu một điểm quan trọng. Bạn có thể truy cập cuối cùng ở trạng thái mặc định của nó (thông qua một phương thức), nhưng trình biên dịch sẽ phàn nàn nếu bạn không khởi tạo nó trước khi kết thúc quá trình xây dựng. Đó là lý do tại sao lần thử thứ hai hoạt động (nó thực sự khởi tạo x), nhưng không phải lần đầu tiên. Trình biên dịch cũng sẽ phàn nàn nếu bạn cố gắng truy cập trực tiếp vào phần cuối cùng trống.
Luca

28

JLS đang nói rằng bạn phải gán giá trị mặc định cho biến thể hiện cuối cùng trống trong hàm tạo (hoặc trong khối khởi tạo khá giống nhau). Đó là lý do tại sao bạn gặp lỗi trong trường hợp đầu tiên. Tuy nhiên, nó không nói rằng bạn không thể truy cập nó trong hàm tạo trước đó. Trông hơi lạ một chút, nhưng bạn có thể truy cập nó trước khi gán và xem giá trị mặc định cho int - 0.

CẬP NHẬT. Như đã đề cập bởi @ I4mpi, JLS xác định quy tắc mà mỗi giá trị chắc chắn phải được chỉ định trước bất kỳ truy cập nào:

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.

Tuy nhiên, nó cũng có một quy tắc thú vị liên quan đến các hàm tạo và các trường:

If C has at least one instance initializer or instance variable initializer then V is [un]assigned after an explicit or implicit superclass constructor invocation if V is [un]assigned after the rightmost instance initializer or instance variable initializer of C.

Vì vậy, trong trường hợp thứ hai giá trị xđược dứt khoát giao vào đầu các nhà xây dựng, bởi vì nó có chứa phân ở phần cuối của nó.


Trên thực tế, không nói rằng bạn không thể truy cập vào nó trước khi chuyển nhượng : "Mỗi biến cục bộ (§14.4) và mọi lĩnh vực thức trống (§4.12.4, §8.3.1.2) phải có một giá trị nhất định chỉ định khi bất kỳ truy cập giá trị của nó xảy ra "
l4mpi

1
nó phải được "chắc chắn giao", tuy nhiên quy tắc này có hành vi kỳ lạ về nhà xây dựng, tôi đã cập nhật câu trả lời
udalmik

Nếu có một phương thức mã nào đó, tùy thuộc vào một số điều kiện phức tạp, có thể đọc hoặc không đọc một finaltrường và nếu mã đó có thể được chạy cả trước và sau khi trường được ghi, thì trong trường hợp chung, một trình biên dịch sẽ không có cách nào biết liệu trên thực tế nó có bao giờ đọc trường trước khi nó được viết hay không.
supercat

7

Nếu bạn không khởi tạo, xbạn sẽ gặp lỗi thời gian biên dịch vì xnó không bao giờ được khởi tạo.

Khai báo xlà cuối cùng có nghĩa là nó có thể được khởi tạo chỉ trong hàm tạo hoặc trong khối khởi tạo (vì khối này sẽ được trình biên dịch sao chép vào mọi hàm tạo).

Lý do mà bạn bị 0in ra trước khi biến được khởi tạo là do hành vi được xác định trong hướng dẫn sử dụng (xem: phần "Giá trị mặc định"):

Giá trị mặc định

Không phải lúc nào cũng cần chỉ định một giá trị khi một trường được khai báo. Các trường được khai báo nhưng không được khởi tạo sẽ được trình biên dịch đặt thành mặc định hợp lý. Nói chung, mặc định này sẽ là 0 hoặc null, tùy thuộc vào kiểu dữ liệu. Tuy nhiên, việc dựa vào các giá trị mặc định như vậy thường được coi là phong cách lập trình tồi.

Biểu đồ sau đây tóm tắt các giá trị mặc định cho các kiểu dữ liệu trên.

Data Type   Default Value (for fields)
--------------------------------------
byte        0
short       0
int         0
long        0L
float       0.0f
double      0.0d
char        '\u0000'
String (or any object)      null
boolean     false

4

Lỗi đầu tiên là trình biên dịch phàn nàn rằng bạn có trường cuối cùng, nhưng không có mã nào để khởi tạo nó - đủ đơn giản.

Trong ví dụ thứ hai, bạn có mã để gán giá trị cho nó, nhưng trình tự thực thi có nghĩa là bạn tham chiếu trường cả trước và sau khi gán nó.

Giá trị được gán trước của bất kỳ trường nào là giá trị mặc định.


2

Tất cả các trường không phải cuối cùng của một lớp đều khởi tạo thành giá trị mặc định ( 0đối với kiểu dữ liệu số, falsekiểu boolean và nullkiểu tham chiếu, đôi khi được gọi là đối tượng phức tạp). Các trường này khởi tạo trước khi một phương thức khởi tạo (hoặc khối khởi tạo thể hiện) thực thi độc lập với việc các trường có được khai báo trước hay sau phương thức khởi tạo hay không.

Các trường cuối cùng của một lớp không có giá trị mặc định và phải được khởi tạo rõ ràng chỉ một lần trước khi một phương thức khởi tạo lớp hoàn thành công việc của mình.

Các biến cục bộ ở bên trong khối thực thi (ví dụ: một phương thức) không có giá trị mặc định. Các trường này phải được khởi tạo rõ ràng trước lần sử dụng đầu tiên và việc biến cục bộ có được đánh dấu là cuối cùng hay không không quan trọng.


1

Hãy để tôi diễn đạt nó bằng những từ đơn giản nhất mà tôi có thể.

finalCác biến cần được khởi tạo, điều này được yêu cầu bởi Đặc tả ngôn ngữ. Đã nói, xin lưu ý rằng không cần thiết phải khởi tạo nó tại thời điểm khai báo.

Nó được yêu cầu khởi tạo trước khi đối tượng được khởi tạo.

Chúng ta có thể sử dụng các khối khởi tạo để khởi tạo các biến cuối cùng. Bây giờ, các khối khởi tạo có hai loại staticnon-static

Khối bạn đã sử dụng là khối khởi tạo không tĩnh. Vì vậy, khi bạn tạo một đối tượng, Runtime sẽ gọi phương thức khởi tạo và đến lượt nó sẽ gọi phương thức khởi tạo của lớp cha.

Sau đó, nó sẽ gọi tất cả các trình khởi tạo (trong trường hợp của bạn là trình khởi tạo không tĩnh).

Trong câu hỏi của bạn, trường hợp 1 : Ngay cả sau khi hoàn thành khối khởi tạo, biến cuối cùng vẫn chưa được khởi tạo, đó là một trình biên dịch lỗi sẽ phát hiện.

Trong trường hợp 2 : Trình khởi tạo sẽ khởi tạo biến cuối cùng, do đó trình biên dịch biết rằng trước khi đối tượng được khởi tạo, biến cuối cùng đã được khởi tạo. Do đó, nó sẽ không phàn nàn.

Bây giờ câu hỏi là, tại sao lại xnhận một số không. Lý do ở đây là trình biên dịch đã biết rằng không có lỗi và vì vậy khi gọi phương thức init, tất cả các phần cuối cùng sẽ được khởi tạo thành mặc định và một bộ cờ mà chúng có thể thay đổi theo câu lệnh gán thực tế tương tự x=7. Xem lời gọi init bên dưới:

nhập mô tả hình ảnh ở đây


1

Theo như tôi biết, trình biên dịch sẽ luôn khởi tạo các biến lớp thành giá trị mặc định (thậm chí cả biến cuối cùng). Ví dụ: nếu bạn khởi tạo một int cho chính nó, int sẽ được đặt thành mặc định là 0. Xem bên dưới:

class Test {
    final int x;

    {
        printX();
        x = this.x;
        printX();
    }

    Test() {
        System.out.println("const called");
    }

    void printX() {
        System.out.println("Here x is " + x);
    }

    public static void main(String[] args) {
        Test t = new Test();
    }
}

Ở trên sẽ in ra như sau:

Here x is 0
Here x is 0
const called

1
Biến cuối cùng x không phải là biến tĩnh trong mã OP.
JamesB

Tôi có thể dễ dàng sửa đổi mã của OP để khởi tạo thành this.x và điều tương tự sẽ xảy ra. Nó không quan trọng nếu nó tĩnh hay không.
Michael D.

Tôi khuyên bạn nên xóa nội dung tĩnh ở đây vì có vẻ như bạn chưa đọc câu hỏi của OP.
JamesB

Nó có ích gì nếu tôi căn cứ từ mã của OP sau đó không? Như tôi đã nói, không quan trọng nếu biến là tĩnh hay không. Quan điểm của tôi là việc khởi tạo một biến cho chính nó và nhận giá trị mặc định ngụ ý rằng biến đó được khởi tạo ngầm định trước khi được khởi tạo rõ ràng.
Michael D.

1
nó không biên dịch, bởi vì bạn đang cố gắng truy cập (trực tiếp) vào một biến cuối cùng trước khi nó được khởi tạo, trên dòng 6.
Luca

1

Nếu tôi cố gắng thực thi nó, tôi gặp lỗi trình biên dịch là: biến x có thể không được khởi tạo dựa trên các giá trị mặc định của java, tôi sẽ nhận được kết quả đầu ra bên dưới đúng không ??

"Ở đây x là 0".

Không. Bạn không nhìn thấy đầu ra đó bởi vì bạn đang gặp lỗi thời gian biên dịch ngay từ đầu. Các biến cuối cùng nhận một giá trị mặc định, nhưng Đặc tả ngôn ngữ Java (JLS) yêu cầu bạn khởi tạo chúng ở cuối hàm tạo (LE: Tôi đang bao gồm các khối khởi tạo ở đây), nếu không bạn sẽ gặp lỗi thời gian biên dịch. sẽ ngăn mã của bạn được biên dịch và thực thi.

Ví dụ thứ hai của bạn tôn trọng yêu cầu, đó là lý do tại sao (1) mã của bạn biên dịch và (2) bạn nhận được hành vi mong đợi.

Trong tương lai, hãy cố gắng làm quen với JLS. Không có nguồn thông tin nào tốt hơn về ngôn ngữ Java.

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.