Khối tĩnh trong Java không được thực thi


87
class Test {
    public static void main(String arg[]) {    
        System.out.println("**MAIN METHOD");
        System.out.println(Mno.VAL); // SOP(9090);
        System.out.println(Mno.VAL + 100); // SOP(9190);
    }

}

class Mno {
    final static int VAL = 9090;
    static {
        System.out.println("**STATIC BLOCK OF Mno\t: " + VAL);
    }
}

Tôi biết rằng một statickhối được thực thi khi lớp được tải. Nhưng trong trường hợp này, biến thể hiện bên trong lớp Mnofinaldo statickhối không thực thi.

Tại sao lại như vậy? Và nếu tôi xóa final, nó có hoạt động tốt không?

Bộ nhớ nào sẽ được cấp phát đầu tiên, static finalbiến hay statickhối?

Nếu do công cụ finalsửa đổi truy cập mà lớp không được tải, thì làm thế nào để biến có thể nhận được bộ nhớ?


1
Lỗi chính xác và thông báo bạn nhận được là gì?
Patashu

@Patashu, không có lỗi, không có gì phải nghi ngờ
Sthita,

Câu trả lời:


134
  1. Một static final inttrường là một hằng số thời gian biên dịch và giá trị của nó được mã hóa cứng vào lớp đích mà không có tham chiếu đến nguồn gốc của nó;
  2. do đó lớp chính của bạn không kích hoạt việc tải lớp chứa trường;
  3. do đó bộ khởi tạo tĩnh trong lớp đó không được thực thi.

Về chi tiết cụ thể, mã bytecode được biên dịch tương ứng với điều này:

public static void main(String arg[]){    
    System.out.println("**MAIN METHOD");
    System.out.println(9090)
    System.out.println(9190)
}

Ngay sau khi bạn xóa final, nó không còn là hằng số thời gian biên dịch nữa và hành vi đặc biệt được mô tả ở trên không áp dụng. Các Mnolớp được nạp như bạn mong đợi và tĩnh của nó initializer thực thi.


1
Nhưng, sau đó làm thế nào để giá trị của biến cuối cùng trong lớp được đánh giá mà không cần tải lớp?
Sumit Desai

18
Tất cả đánh giá xảy ra tại thời điểm biên dịch và kết quả cuối cùng được mã hóa cứng vào tất cả các nơi tham chiếu đến biến.
Marko Topolnik

1
Vì vậy, nếu thay vì một biến nguyên thủy, nó là một Đối tượng nào đó, thì việc mã hóa cứng như vậy sẽ không thể thực hiện được. Phải không? Vì vậy, trong trường hợp đó, lớp đó sẽ được tải và khối tĩnh sẽ được thực thi?
Sumit Desai

2
Marko, nghi ngờ của Sumit cũng đúng nếu thay vì nguyên thủy, nó là một Đối tượng nào đó, thì việc mã hóa cứng như vậy sẽ không thể thực hiện được. Phải không? Vì vậy, trong trường hợp đó, lớp đó sẽ được tải và khối tĩnh sẽ được thực thi?
Sthita

8
@SumitDesai Chính xác, điều này chỉ hoạt động đối với các giá trị nguyên thủy và các ký tự chuỗi. Để biết chi tiết đầy đủ, hãy đọc chương liên quan trong Đặc tả ngôn ngữ Java
Marko Topolnik,

8

Lý do tại sao lớp không được tải VALfinal AND nó được khởi tạo bằng một biểu thức không đổi (9090). Nếu và chỉ khi, hai điều kiện đó được đáp ứng, hằng số được đánh giá tại thời điểm biên dịch và được "mã hóa cứng" khi cần thiết.

Để ngăn biểu thức được đánh giá tại thời điểm biên dịch (và để làm cho JVM tải lớp của bạn), bạn có thể:

  • xóa từ khóa cuối cùng:

    static int VAL = 9090; //not a constant variable any more
    
  • hoặc thay đổi biểu thức bên phải thành một cái gì đó không phải là hằng số (ngay cả khi biến vẫn là cuối cùng):

    final static int VAL = getInt(); //not a constant expression any more
    static int getInt() { return 9090; }
    

5

Nếu bạn thấy mã bytecode được tạo bằng cách sử dụng javap -v Test.class, main () xuất hiện như sau:

public static void main(java.lang.String[]) throws java.lang.Exception;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String **MAIN METHOD
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: sipush        9090
        14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: sipush        9190
        23: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        26: return        

Bạn có thể thấy rõ trong " 11: sipush 9090" rằng giá trị cuối cùng tĩnh được sử dụng trực tiếp, vì Mno.VAL là hằng số thời gian biên dịch. Do đó không bắt buộc phải nạp lớp Mno. Do đó khối tĩnh của Mno không được thực thi.

Bạn có thể thực thi khối tĩnh bằng cách tải Mno theo cách thủ công như sau:

class Test{
    public static void main(String arg[]) throws Exception {
        System.out.println("**MAIN METHOD");
        Class.forName("Mno");                 // Load Mno
        System.out.println(Mno.VAL);
        System.out.println(Mno.VAL+100);
    }

}

class Mno{
    final static int VAL=9090;
    static{
        System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
    }
}

1
  1. Thực ra bạn chưa mở rộng lớp Mno đó nên khi bắt đầu biên dịch, nó sẽ tạo ra hằng số của biến VAL và khi thực thi bắt đầu khi cần tải biến đó từ bộ nhớ. Vì vậy, nó không yêu cầu tham chiếu lớp của bạn để bock tĩnh không được thực thi.

  2. nếu lớp Amở rộng lớp Mno, khối tĩnh được bao gồm trong lớp Anếu bạn làm điều này thì khối tĩnh đó được thực thi. Ví dụ..

    public class A extends Mno {
    
        public static void main(String arg[]){    
            System.out.println("**MAIN METHOD");
            System.out.println(Mno.VAL);//SOP(9090);
            System.out.println(Mno.VAL+100);//SOP(9190);
        }
    
    }
    
    class Mno {
        final static int VAL=9090;
        static {
            System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
        }
    }
    

0

Theo như tôi biết, nó sẽ được thực hiện theo thứ tự xuất hiện. Ví dụ :

 public class Statique {
     public static final String value1 = init1();

     static {
         System.out.println("trace middle");
     }
     public static final String value2 = init2();


     public static String init1() {
         System.out.println("trace init1");
         return "1";
     }
     public static String init2() {
         System.out.println("trace init2");
         return "2";
     }
 }

sẽ in

  trace init1
  trace middle
  trace init2

Tôi vừa thử nghiệm nó và các tĩnh được khởi tạo (=> in) khi lớp "Statique" thực sự được sử dụng và "thực thi" trong một đoạn mã khác (trường hợp của tôi, tôi đã làm "new Statique ()".


2
Bạn nhận được kết quả này bởi vì bạn đang tải Statiquelớp bằng cách thực hiện new Statique(). Trong khi câu hỏi được hỏi, Mnolớp hoàn toàn không được tải.
RAS

@Fabyen, nếu tôi đang tạo đối tượng của Mno trong lớp thử nghiệm như thế này: Mno anc = New Mno (); thì nó tốt, nhưng kịch bản hiện tại tôi không làm điều đó, nghi ngờ của tôi là nếu tôi đang loại bỏ cuối cùng thì khối tĩnh thực thi tốt nếu không nó không thực thi, tại sao vậy ??
Sthita,

1
Có câu trả lời dưới đây là hoàn hảo. Trong bytecode của Main.class (sử dụng Mno.VAL), 9090 được tìm thấy được mã hóa cứng. Loại bỏ cuối cùng, biên dịch, sau đó sử dụng javap Main, bạn sẽ thấy getstatic # 16; // Dòng Statique.VAL: Tôi . Đặt lại cuối cùng, biên dịch, sau đó sử dụng javap Main, bạn sẽ thấy nhâm nhi 9090 .
Fabyen

1
Vì nó được mã hóa cứng trong Main.class, không có lý do gì để tải lớp MNO, do đó không có khởi tạo tĩnh.
Fabyen

Điều này trả lời câu hỏi thứ hai: "Bộ nhớ nào sẽ được cấp phát đầu tiên, biến tĩnh cuối cùng hay khối tĩnh?" (thứ tự từ vựng)
Hauke ​​Ingmar Schmidt
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.