Công cụ sửa đổi tĩnh ảnh hưởng đến mã này như thế nào?


109

Đây là mã của tôi:

class A {
    static A obj = new A();
    static int num1;
    static int num2=0;

    private A() {
        num1++;
        num2++;
    }
    public static A getInstance() {
        return obj;
    }
}

public class Main{
    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

Kết quả là 1 0, nhưng tôi không thể hiểu được.

Ai đó có thể giải thích cho tôi được không?


10
Câu hỏi hay! Chúng ta nên học gì từ điều này: Đừng làm điều đó! ;)
isnot2bad

Câu trả lời:


116

Trong Java, hai giai đoạn diễn ra: 1. Nhận dạng, 2. Thực thi

  1. Trong nhận dạng giai đoạn tất cả các biến tĩnh được phát hiện và khởi tạo với các giá trị mặc định.

    Vì vậy, bây giờ các giá trị là:
    A obj=null
    num1=0
    num2=0

  2. Giai đoạn thứ hai, thực hiện , bắt đầu từ trên xuống dưới. Trong Java, việc thực thi bắt đầu từ các thành viên tĩnh đầu tiên.
    Đây là biến static đầu tiên của bạn static A obj = new A();, vì vậy trước tiên nó sẽ tạo đối tượng của biến đó và gọi hàm tạo, do đó giá trị của num1num2trở thành 1.
    Và sau đó, một lần nữa, static int num2=0;sẽ được thực hiện num2 = 0;.

Bây giờ, giả sử hàm tạo của bạn như sau:

 private A(){
    num1++;
    num2++;
    System.out.println(obj.toString());
 }

Điều này sẽ ném một NullPointerExceptionobjvẫn chưa có tham chiếu đến class A.


11
Tôi sẽ mở rộng: di chuyển dòng static A obj = new A();bên dưới static int num2=0;và bạn sẽ nhận được 1 và 1.
Thomas

2
Có gì vẫn bối rối cho tôi là một thực tế rằng mặc dù num1 không có một khởi rõ ràng, nó IS (implicitely) khởi tạo với 0. Có thực sự cần phải có sự khác biệt giữa khởi ngầm và rõ ràng ...
isnot2bad

@ isnot2bad "khởi tạo ngầm định" xảy ra như một phần của khai báo. Tuyên bố xảy ra trước khi chuyển nhượng không có vấn đề gì theo thứ tự bạn trình bày chúng trong. A obj = new A(); int num1; int num2 = 0;Bị biến thành này: A obj; int num1; int num2; obj = new A(); num2 = 0;. Java thực hiện điều này do đó num1, num2được xác định vào thời điểm bạn tiếp cận hàm new A()tạo.
Hans Z

31

Ý staticnghĩa của công cụ sửa đổi khi áp dụng cho khai báo biến là biến đó là một biến lớp chứ không phải là một biến thể hiện. Nói cách khác ... chỉ có một num1biến, và chỉ một num2biến.

(Ngoài ra: biến tĩnh giống như một biến toàn cục trong một số ngôn ngữ khác, ngoại trừ việc tên của nó không hiển thị ở mọi nơi. Ngay cả khi nó được khai báo làpublic static , thì tên không đủ tiêu chuẩn chỉ hiển thị nếu nó được khai báo trong lớp hiện tại hoặc lớp cha hoặc nếu nó được nhập bằng cách nhập tĩnh. Đó là sự khác biệt. Một toàn cầu thực có thể nhìn thấy mà không cần đủ điều kiện ở bất kỳ đâu.)

Vì vậy, khi bạn tham chiếu đến obj.num1obj.num2, bạn thực sự đang đề cập đến các biến tĩnh có ký hiệu thực là A.num1A.num2. Và tương tự, khi hàm tạo tăng num1num2, nó đang tăng các biến giống nhau (tương ứng).

Nếp nhăn khó hiểu trong ví dụ của bạn là ở phần khởi tạo lớp. Một lớp được khởi tạo theo mặc định đầu tiên khởi tạo tất cả các biến tĩnh, sau đó thực thi các bộ khởi tạo tĩnh đã khai báo (và các khối khởi tạo tĩnh) theo thứ tự xuất hiện trong lớp. Trong trường hợp này, bạn có:

static A obj = new A();
static int num1;
static int num2=0;

Nó xảy ra như thế này:

  1. Các tĩnh bắt đầu với các giá trị ban đầu mặc định của chúng; A.objnullA.num1/ A.num2bằng không.

  2. Khai báo đầu tiên ( A.obj) tạo một thể hiện của A(), và phương thức khởi tạo cho các giá trị Agia tăng A.num1A.num2. Khi khai báo hoàn thành, A.num1A.num2cả hai 1, và A.objtham chiếu đến Acá thể mới được xây dựng .

  3. Khai báo thứ hai ( A.num1) không có bộ khởi tạo, vì vậy A.num1không thay đổi.

  4. Khai báo thứ ba ( A.num2) có một bộ khởi tạo gán giá trị 0 cho A.num2.

Do đó, ở phần cuối của việc khởi tạo lớp, A.num11A.num20... và đó là những gì báo cáo in của bạn hiển thị.

Hành vi khó hiểu này thực sự là do bạn đang tạo một cá thể trước khi quá trình khởi tạo tĩnh hoàn tất và phương thức khởi tạo bạn đang sử dụng phụ thuộc và sửa đổi một tĩnh chưa được khởi tạo. Đây là điều mà bạn nên tránh làm trong mã thực.


16

1,0 là đúng.

Khi lớp được tải, tất cả dữ liệu tĩnh được khởi tạo trong oder, chúng được khai báo. Theo mặc định int là 0.

  • đầu tiên A được tạo. num1 và num2 trở thành 1 và 1
  • còn hơn static int num1;không
  • hơn static int num2=0;điều này ghi 0 thành num2

9

Đó là do thứ tự của các bộ khởi tạo tĩnh. Biểu thức static trong các lớp được đánh giá theo thứ tự từ trên xuống.

Đầu tiên được gọi là hàm tạo của A, đặt num1num2cả hai thành 1:

static A obj = new A();

Sau đó,

static int num2=0;

được gọi và đặt lại num2 = 0.

Đó là lý do tại sao num1là 1 và num2là 0.

Lưu ý thêm, một hàm tạo không nên sửa đổi các biến tĩnh, đó là thiết kế rất tệ. Thay vào đó, hãy thử một cách tiếp cận khác để triển khai Singleton trong Java .


6

Một phần trong JLS có thể được tìm thấy: §12.4.2 .

Quy trình khởi tạo chi tiết:

9. Tiếp theo, thực thi các bộ khởi tạo biến lớp và bộ khởi tạo tĩnh của lớp hoặc các bộ khởi tạo trường của giao diện, theo thứ tự văn bản, như thể chúng là một khối duy nhất, ngoại trừ các biến lớp cuối cùng và các trường của giao diện có giá trị được biên dịch hằng số thời gian được khởi tạo đầu tiên

Vì vậy, ba biến tĩnh sẽ được khởi tạo lần lượt theo thứ tự văn bản.

Vì thế

static A obj = new A();
//num1 = 1, num2 = 1;
static int num1;
//this is initilized first, see below.
static int num2=0;
//num1 = 1, num2 = 0;

Nếu tôi thay đổi thứ tự thành:

static int num1;
static int num2=0;
static A obj = new A();

Kết quả sẽ là 1,1.

Lưu ý rằng static int num1;không phải là một bộ khởi tạo biến vì ( §8.3.2 ):

Nếu bộ khai báo trường chứa bộ khởi tạo biến thì nó có ngữ nghĩa của một phép gán (§15.26) cho biến đã khai báo và: Nếu bộ khai báo dành cho một biến lớp (tức là trường tĩnh), thì bộ khởi tạo biến đó là đã đánh giá và bài tập được thực hiện chính xác một lần, khi lớp được khởi tạo

Và biến lớp này được khởi tạo khi lớp được tạo. Điều này xảy ra đầu tiên ( §4.12.5 ).

Mọi biến trong chương trình phải có một giá trị trước khi giá trị của nó được sử dụng: Mỗi biến lớp, biến thể hiện hoặc thành phần mảng được khởi tạo với giá trị mặc định khi nó được tạo (§15.9, §15.10): Đối với kiểu byte, giá trị mặc định là 0, tức là giá trị của (byte) 0. Đối với kiểu short, giá trị mặc định là 0, nghĩa là giá trị (short) 0. Đối với kiểu int, giá trị mặc định là 0, nghĩa là 0. Đối với kiểu long, giá trị mặc định là 0, nghĩa là 0L. Đối với kiểu float, giá trị mặc định là số 0 dương, nghĩa là 0,0f. Đối với loại double, giá trị mặc định là số không dương, nghĩa là 0,0 ngày. Đối với kiểu char, giá trị mặc định là ký tự rỗng, nghĩa là '\ u0000'. Đối với kiểu boolean, giá trị mặc định là false. Đối với tất cả các kiểu tham chiếu (§4.3), giá trị mặc định là null.


2

Có lẽ nó sẽ hữu ích khi nghĩ về nó theo cách này.

Các lớp là bản thiết kế cho các đối tượng.

Các đối tượng có thể có các biến khi chúng được khởi tạo.

Các lớp cũng có thể có các biến. Chúng được khai báo là tĩnh. Vì vậy, chúng được đặt trên lớp chứ không phải là các cá thể đối tượng.

Bạn chỉ có thể có một trong số bất kỳ lớp nào trong một ứng dụng, vì vậy nó giống như bộ nhớ chung dành riêng cho lớp đó. Tất nhiên, các biến tĩnh này có thể được truy cập và sửa đổi từ bất kỳ đâu trong ứng dụng của bạn (giả sử chúng là công khai).

Đây là ví dụ về lớp "Dog" sử dụng biến tĩnh để theo dõi số lượng cá thể mà nó đã tạo.

Lớp "Dog" là đám mây trong khi các hộp màu Cam là các thể hiện "Dog".

Lớp chó

đọc thêm

Hi vọng điêu nay co ich!

Nếu bạn cảm thấy giống như một số câu đố, ý tưởng này lần đầu tiên được giới thiệu bởi Plato


1

Từ khóa static được sử dụng trong java chủ yếu để quản lý bộ nhớ. Chúng tôi có thể áp dụng từ khóa static với các biến, phương thức, khối và lớp lồng nhau. Từ khóa static thuộc về lớp chứ không phải thể hiện của lớp. Để giải thích ngắn gọn về từ khóa static:

http://www.javatpoint.com/static-keyword-in-java


0

Nhiều câu trả lời ở trên là đúng. Nhưng thực sự để minh họa những gì đang xảy ra, tôi đã thực hiện một số sửa đổi nhỏ bên dưới.

Như đã đề cập nhiều lần ở trên, những gì đang xảy ra là một thể hiện của lớp A đang được tạo trước khi lớp A được tải đầy đủ. Vì vậy, những gì được coi là 'hành vi' bình thường không được quan sát. Điều này không quá khác với việc gọi các phương thức từ một hàm tạo có thể bị ghi đè. Trong trường hợp đó, các biến cá thể có thể không ở trạng thái trực quan. Trong ví dụ này, các biến lớp không ở trạng thái trực quan.

class A {
    static A obj = new A();
    static int num1;
    static int num2;
    static {
        System.out.println("Setting num2 to 0");
        num2 = 0;
    }

    private A() {
        System.out.println("Constructing singleton instance of A");
        num1++;
        num2++;
    }

    public static A getInstance() {
        return obj;
    }
}

public class Main {

    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

Đầu ra là

Constructing singleton instance of A
Setting num2 to 0
1
0

0

java không khởi tạo giá trị của bất kỳ thành viên dữ liệu tĩnh hoặc không tĩnh nào cho đến khi nó không được gọi nhưng nó tạo ra nó.

Vì vậy, ở đây khi num1 và num2 sẽ được gọi trong main thì nó sẽ được khởi tạo với các giá trị

num1 = 0 + 1; và

num2 = 0;

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.