【问题标题】:Static block in Java not executedJava中的静态块未执行
【发布时间】:2014-06-05 16:36:48
【问题描述】:
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 块。但在这种情况下,类Mno 中的实例变量是final,因为static 块没有执行。

为什么会这样?如果我删除final,它可以正常工作吗?

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

如果由于final访问修饰符导致类没有被加载,那么变量如何获得内存?

【问题讨论】:

  • 您得到的确切错误和消息是什么?
  • @Patashu ,没有错误,这是一个疑问

标签: java static access-modifiers


【解决方案1】:
  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 类按预期加载,并执行其静态初始化程序。

【讨论】:

  • 但是,那么如何在不加载类的情况下评估类中final变量的值呢?
  • 所有评估都发生在编译时,最终结果被硬编码到所有引用变量的地方。
  • 所以,如果不是原始变量,而是某个对象,那么这样的硬编码将是不可能的。不是吗?那么,在这种情况下,会加载该类并执行静态块吗?
  • Marko,Sumit 的怀疑也是对的,如果它不是原始的,而是某个对象,那么这样的硬编码将是不可能的。不是吗?那么,在这种情况下,会加载该类并执行静态块吗?
  • @SumitDesai 没错,这仅适用于原始值和字符串文字。有关详细信息,请阅读relevant chapter in the Java Language Specification
【解决方案2】:

类没有加载的原因是VALfinalAND它是用a constant expression(9090)初始化的。当且仅当满足这两个条件时,才会在编译时评估常量并在需要时“硬编码”。

为了防止在编译时计算表达式(并使 JVM 加载您的类),您可以:

  • 去掉final关键字:

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

【讨论】:

    【解决方案3】:

    如果您看到使用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);
        }
    }
    

    【讨论】:

      【解决方案4】:
      1. 实际上,您还没有扩展该 Mno 类,因此当编译开始时,它将生成变量 VAL 的常量,并且当需要该变量时开始执行时,它从内存中加载。所以它不需要你的类引用以便不执行静态块。

      2. 如果A 类扩展Mno 类,则静态块包含在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);
            }
        }
        

      【讨论】:

        【解决方案5】:

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

         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()”。

        【讨论】:

        • 你得到这个输出是因为你正在通过 new Statique() 加载 Statique 类。在提出的问题中,Mno 类根本没有加载。
        • @Fabyen,如果我在这样的测试类中创建 Mno 对象: Mno anc=New Mno();那么它很好,但是目前的情况我没有这样做,我的疑问是如果我删除 final 那么静态块执行得很好,否则它不会执行,为什么会这样??
        • 是的,下面的答案是完美的。在 Main.class(使用 Mno.VAL)的字节码中,发现 9090 是硬编码的。去掉final,编译,然后使用javap Main,你会看到getstatic #16; //字段 Statique.VAL:I。放回final,编译,然后使用javap Main,会看到sipush 9090
        • 因为它是在 Main.class 中硬编码的,所以没有理由加载类 MNO,因此没有静态初始化。
        • 这回答了第二个问题:“首先分配哪个内存,静态最终变量还是静态块?” (词序)
        猜你喜欢
        • 2022-12-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-25
        • 1970-01-01
        相关资源
        最近更新 更多