Java中的静态块未执行


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);
    }
}

我知道static在加载类时执行了一个块。但是在这种情况下,类内的实例变量Mnofinal,因为该static块未执行。

为什么呢?如果我删除final,它会正常工作吗?

首先分配哪个内存,static final变量还是static块?

如果由于final访问修饰符而无法加载该类,那么该变量如何获取内存?


1
您得到的确切错误和消息是什么?
Patashu

@Patashu,没有错误,这是一个疑问
Sthita 2013年

Answers:


134
  1. static final int字段是一个编译时间常数和它的值被硬编码到而不到其原点的参考目标类;
  2. 因此,您的主类不会触发包含该字段的类的加载;
  3. 因此,不会执行该类中的静态初始化程序。

具体而言,编译后的字节码与此对应:

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

删除后final,它就不再是编译时常量,并且上述特殊行为也不适用。在Mno像您期望和类加载其静态初始化执行。


1
但是,如何在不加载类的情况下评估类中最终变量的值呢?
Sumit Desai

18
所有评估都在编译时进行,并且最终结果被硬编码到引用该变量的所有位置。
Marko Topolnik

1
因此,如果不是原始变量,而是某些Object,则将无法进行这种硬编码。是不是 那么,在那种情况下,将加载该类并执行静态块吗?
Sumit Desai

2
Marko,Sumit的怀疑也是正确的,如果不是原始的而是某个Object,那么这样的硬编码将是不可能的。是不是 那么,在那种情况下,将加载该类并执行静态块吗?
Sthita

8
@SumitDesai确实,这仅适用于原始值和字符串文字。有关详细信息,请阅读Java语言规范中
Marko Topolnik,2013年

8

为什么没有加载类的原因是,VALfinal 它被初始化与常量表达式(9090)。当且仅当满足这两个条件时,才在编译时评估该常数,并在需要时对其进行“硬编码”。

为了防止在编译时对表达式求值(并使JVM加载您的类),可以执行以下任一操作:

  • 删除最终关键字:

    static int VAL = 9090; //not a constant variable any more
    
  • 或将右侧表达式更改为非常量(即使变量仍是最终变量):

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

5

如果您看到使用生成的字节码javap -v Test.class,则main()如下所示:

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        

您可以在“11: sipush 9090 ”中直接使用了静态最终值,因为Mno.VAL是一个编译时间常数。因此,不需要加载Mno类。因此,不会执行Mno的静态块。

您可以通过手动加载Mno来执行静态块,如下所示:

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. 实际上,您没有扩展该Mno类,因此在编译开始时它将生成变量VAL的常量,而在执行开始时需要该变量时,它将从内存中加载。因此,不需要您的类引用来执行静态Bock。

  2. 如果classA扩展class Mno,则静态块包含在class中,A如果执行此操作,则将执行该静态块。例如..

    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

据我所知,它将按照出现的顺序执行。例如 :

 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";
     }
 }

将打印

  trace init1
  trace middle
  trace init2

我刚刚对其进行了测试,并在其他代码段中实际使用了“ Statique”类并对其进行了“静态执行”(=>打印)(在我的案例中,我做了“ new Statique()”)。


2
之所以得到此输出,是因为您正在通过加载Statiquenew Statique()。在提出问题时,Mno根本不会加载类。
RAS

@Fabyen,如果我正在像这样的测试类中创建Mno的对象:Mno anc = New Mno(); 然后它很好,但是在当前情况下我没有这样做,我的疑问是,如果我要删除final,那么静态块可以很好地执行,否则它就不执行,为什么呢?
Sthita

1
是的,下面的答案是完美的。在Main.class的字节码中(使用Mno.VAL),发现9090是硬编码的。删除final,进行编译,然后使用javap Main,您将看到getstatic#16;。// Field Statique.VAL:I。放回final,编译,然后使用javap Main,您将看到sipush 9090
Fabyen

1
因为它在Main.class中是硬编码的,所以没有理由加载类MNO,因此没有静态初始化。
Fabyen

这回答了第二个问题:“将首先分配哪个内存,静态最终变量还是静态块?” (词汇​​顺序)
Hauke Ingmar Schmidt
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.