静态修改器和静态块的区别

有人向我解释下面两个陈述之间的区别?

static代码块初始化的static finalvariables:

 private static final String foo; static { foo = "foo"; } 

一个由赋值初始化的static finalvariables:

 private static final String foo = "foo"; 

在这个例子中,有一个微妙的区别 – 在你的第一个例子中, foo不是一个编译时常量,所以在switch块中它不能被用作一个例子(并且不会被内联到其他代码中); 在你的第二个例子中,是。 举个例子:

 switch (args[0]) { case foo: System.out.println("Yes"); break; } 

foo被认为是一个常量expression式时,这是有效的,但是当它只是一个静态的最终variables时不是。

但是,当您有更复杂的初始化代码时(例如填充集合), 通常会使用静态初始化块。

JLS 12.4.2中描述了初始化的时间 ; 任何被认为是编译时常量的静态最终字段将被首先初始化(步骤6),然后初始化程序将在稍后运行(步骤9)。 所有初始化器(无论它们是字段初始化器还是静态初始化器)均按文本顺序运行。

  private static final String foo; static { foo ="foo";} 

foo的值在加载类和运行静态初始值设定项时被初始化

 private static final String foo = "foo"; 

在这里, foo的值是编译时常量。 所以,实际上"foo"将作为字节码本身的一部分。

在II的情况下, foo的是早期绑定,即编译器识别并将值foo赋值给variablesFOO ,该variables不能被改变, 除了字节码本身之外,这将是可用的

 private static final String FOO = "foo"; 

并且在Ist的case-value中将foo初始化之后的类加载作为第一个赋值之前的实例variables赋值,这里也可以 通过调用静态方法来调用静态方法分配静态字段。

 private static final String FOO; static { FOO ="foo";} 

所以, 当编译器必须识别variablesfoo 的值时,只要有条件到达,条件II就会起作用,例如在开关情况下的情况

JLS描述了它所称的常量variables的一些特殊行为,它们是finalvariables(无论是否为staticvariables),它们是用String或原始types的常量expression式初始化的。

常量variables与二进制兼容性有很大的区别:就编译器而言,常量variables的成为类的一部分。

一个例子:

 class X { public static final String XFOO = "xfoo"; } class Y { public static final String YFOO; static { YFOO = "yfoo"; } } class Z { public static void main(String[] args) { System.out.println(X.XFOO); System.out.println(Y.YFOO); } } 

在这里, XFOO是一个“常量variables”, YFOO不是,但是它们是等价的。 Z类打印出每一个。 编译这些类,然后用javap -v XYZ反汇编它们,这里是输出:

X类:

 Constant pool: #1 = Methodref #3.#11 // java/lang/Object."<init>":()V #2 = Class #12 // X #3 = Class #13 // java/lang/Object #4 = Utf8 XFOO #5 = Utf8 Ljava/lang/String; #6 = Utf8 ConstantValue #7 = String #14 // xfoo #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = NameAndType #8:#9 // "<init>":()V #12 = Utf8 X #13 = Utf8 java/lang/Object #14 = Utf8 xfoo { public static final java.lang.String XFOO; descriptor: Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: String xfoo X(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return } 

Y类:

 Constant pool: #1 = Methodref #5.#12 // java/lang/Object."<init>":()V #2 = String #13 // yfoo #3 = Fieldref #4.#14 // Y.YFOO:Ljava/lang/String; #4 = Class #15 // Y #5 = Class #16 // java/lang/Object #6 = Utf8 YFOO #7 = Utf8 Ljava/lang/String; #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 <clinit> #12 = NameAndType #8:#9 // "<init>":()V #13 = Utf8 yfoo #14 = NameAndType #6:#7 // YFOO:Ljava/lang/String; #15 = Utf8 Y #16 = Utf8 java/lang/Object { public static final java.lang.String YFOO; descriptor: Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL Y(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #2 // String yfoo 2: putstatic #3 // Field YFOO:Ljava/lang/String; 5: return } 

Z类:

 Constant pool: #1 = Methodref #8.#14 // java/lang/Object."<init>":()V #2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream; #3 = Class #17 // X #4 = String #18 // xfoo #5 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Fieldref #21.#22 // Y.YFOO:Ljava/lang/String; #7 = Class #23 // Z #8 = Class #24 // java/lang/Object #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 main #13 = Utf8 ([Ljava/lang/String;)V #14 = NameAndType #9:#10 // "<init>":()V #15 = Class #25 // java/lang/System #16 = NameAndType #26:#27 // out:Ljava/io/PrintStream; #17 = Utf8 X #18 = Utf8 xfoo #19 = Class #28 // java/io/PrintStream #20 = NameAndType #29:#30 // println:(Ljava/lang/String;)V #21 = Class #31 // Y #22 = NameAndType #32:#33 // YFOO:Ljava/lang/String; #23 = Utf8 Z #24 = Utf8 java/lang/Object #25 = Utf8 java/lang/System #26 = Utf8 out #27 = Utf8 Ljava/io/PrintStream; #28 = Utf8 java/io/PrintStream #29 = Utf8 println #30 = Utf8 (Ljava/lang/String;)V #31 = Utf8 Y #32 = Utf8 YFOO #33 = Utf8 Ljava/lang/String; { Z(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V 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 #4 // String xfoo 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 11: getstatic #6 // Field Y.YFOO:Ljava/lang/String; 14: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 17: return } 

在反汇编中要注意的是, XY之间的区别比语法糖更深刻:

  • XFOO有一个ConstantValue属性,表示它的值是编译时常量。 而YFOO则没有,并且使用static模块和putstatic指令来在运行时初始化数值。

  • String常量"xfoo"已成为Z类常量池的一部分 ,但"yfoo"没有。

  • Z.main使用ldc (load constant)指令直接从自己的常量池中将"xfoo"加载到堆栈上,但它使用getstatic指令加载Y.YFOO的值。

其他区别你会发现:

  • 如果更改XFOO的值并重新编译X.java而不是Z.java ,则会出现问题:类Z仍在使用旧值。 如果您更改YFOO的值并重新编译Y.java ,则不pipe您是否重新编译Y.java ,类Z将使用新值。

  • 如果完全删除X.class文件,则类Z仍然正常运行。 ZX上没有运行时依赖。 而如果删除Y.class文件,则类Z无法使用ClassNotFoundException: Y进行初始化。

  • 如果使用javadoc为类生成文档,则“常量字段值”页面将loggingXFOO的值,但不loggingYFOO的值。

JLS描述了常量variables对§13.1.3中已编译的类文件的影响 :

对常量variables(§4.12.4)的字段的引用必须在编译时parsing为由常量variables初始值表示的值V.

如果这样的字段是static ,那么在二进制文件的代码中不应该包含字段的引用,包括声明该字段的类或接口。 这样的字段必须总是被初始化(§12.4.2); 永远不能观察字段的默认初始值(如果不同于V)。

如果这样的字段是非static ,那么除了包含字段的类以外,在二进制文件的代码中不应该存在对该字段的引用。 (它将是一个类而不是一个接口,因为一个接口只有static字段。)在创build实例时(§12.5),类应该有代码来将字段的值设置为V.

在§13.4.9中 :

如果一个字段是一个常量variables(§4.12.4),而且是static ,那么删除关键字final或者改变它的值不会破坏与预先存在的二进制文件的兼容性,但是它们不会看到任何除非重新编译,否则使用该字段的新值。

[…]

避免广泛分布代码中的“不定常量”问题的最佳方法是仅将static常量variables用于真正不太可能改变的值。 除了真正的math常量之外,我们build议源代码非常less地使用static常量variables。

结果是,如果你的公共库暴露了任何常量variables,那么如果你的新库版本应该与针对老版本库编译的代码兼容,则决不能改变它们的值。 它不一定会导致错误,但是现有的代码可能会出现故障,因为它会对常量的值产生过时的想法。 (如果你的新库版本需要使用它的类来重新编译,那么改变常量不会导致这个问题。)

因此,用一个块初始化一个常量给你更多的自由来改变它的值,因为它可以防止编译器把值embedded到其他类中。

唯一的区别是初始化时间。

Java首先初始化成员,然后是静态块。

另外一个方面:当你有多个静态字段的时候考虑这种情况,而且这是一个特例。

正如Jon Skeet的回答所述,JLS定义了初始化的确切顺序。 但是,如果由于某种原因必须以特定顺序初始化多个静态属性,则可能需要使代码中的初始化顺序清晰可见。 使用直接字段初始化时:某些代码格式化程序(和开发人员)可能在某些时候决定对字段进行不同的sorting,这将直接影响字段如何初始化并引入不需要的效果。

顺便说一下,如果你想遵循常见的Java编码惯例,你应该在定义“常量”(最终静态字段)时使用大写字母。

—编辑反映了Jon Skeet的评论—

静态块给你的不仅仅是简单的语句。 在这个特定的情况下是同样的事情。 在构造任何实例之前,静态部分将在类加载时执行。 您可以在这里调用方法并将其结果分配给静态字段。 你可以在静态块中捕获exception。