有人向我解释了以下两个陈述之间的区别吗?
由static final
代码块初始化的static
变量:
private static final String foo;
static { foo = "foo"; }
由作业初始化的static final
变量:
private static final String foo = "foo";
答案 0 :(得分:100)
在这个示例中,有一个微妙的区别 - 在您的第一个示例中,foo
并未确定为编译时常量,因此它可以& #39; t用作switch
块中的大小写(并且不会被内联到其他代码中);在你的第二个例子中,它是。例如:
switch (args[0]) {
case foo:
System.out.println("Yes");
break;
}
当foo
被认为是一个不变的表达时,这是有效的,但是当它被认为是"只是"一个静态的最终变量。
但是,当您有更复杂的初始化代码时,静态初始化程序块通常 ,例如填充集合。
初始化的时间在JLS 12.4.2中描述;任何被认为是编译时常量的静态最终字段首先被初始化(步骤6)并且初始化器被稍后运行(步骤9);所有初始值设定项(无论是它们的字段初始值设定项还是静态初始值设定项)都以文本顺序运行。
答案 1 :(得分:34)
private static final String foo;
static { foo ="foo";}
当加载并且运行静态初始值设定项时,foo
的值已初始化 。
private static final String foo = "foo";
此处,foo
的值将是编译时常量。因此,实际上"foo"
将作为字节代码本身的一部分提供。
答案 2 :(得分:9)
在IInd情况下 - foo的值是早期绑定即编译器识别并将值foo赋值给变量FOO
,这些变量不能被更改,并且这将是可用的字节码本身。
private static final String FOO = "foo";
并且在Ist的情况下,foo初始化刚刚在类加载后作为分配实例变量之前的第一个赋值,在这里你可以捕获异常或静态字段可以通过在静态块中调用静态方法来分配。
private static final String FOO;
static { FOO ="foo";}
因此,只要有条件到达,当编译器必须识别变量foo的值时,条件II将起作用,因为ex- 类似于case的值:在switch case 强>
答案 3 :(得分:8)
JLS描述了它所谓的常量变量的一些特殊行为,这些行为是final
变量(无论是否为static
),它们是用{的常量表达式初始化的{1}}或原始类型。
常量变量在二进制兼容性方面存在重大差异:就编译器而言,常量变量的值成为类API的一部分。
一个例子:
String
此处,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
不是,但它们在其他方面是等效的。类YFOO
打印出每个类。编译这些类,然后用Z
反汇编它们,这是输出:
X类:
javap -v X Y Z
Y班:
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
}
Z级:
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
}
反汇编中需要注意的事项,它告诉你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
}
和X
之间的区别比语法糖更深:
Y
具有ConstantValue
属性,表示其值为编译时常量。而XFOO
没有,并使用YFOO
块和static
指令在运行时初始化值。
putstatic
常量String
已成为类"xfoo"
常量池的一部分,但Z
没有。{ / p>
"yfoo"
使用Z.main
(加载常量)指令将ldc
直接从其自己的常量池加载到堆栈中,但它使用"xfoo"
指令加载getstatic
。
您会发现其他差异:
如果更改Y.YFOO
的值并重新编译XFOO
而不是X.java
,则会出现问题:类Z.java
仍在使用旧值。如果您更改Z
的值并重新编译YFOO
,则无论您是否重新编译Y.java
,类Z
都会使用新值。
如果您完全删除Z.java
文件,则类X.class
仍可正常运行。 Z
对Z
没有运行时依赖性。如果删除X
文件,则类Y.class
无法使用{{1}初始化}。
如果使用javadoc生成类的文档,“常量字段值”页面将记录Z
的值,但不记录ClassNotFoundException: Y
的值。
JLS描述了常量变量对§13.1.3中编译的类文件的上述影响:
对于作为常量变量(§4.12.4)的字段的引用必须在编译时解析为由常量变量的初始化程序表示的值V.
如果这样的字段是
。必须永远不要观察该字段的默认初始值(如果不同于V)。XFOO
,那么二进制文件中的代码中不应该存在对该字段的引用,包括声明该字段的类或接口。这样的字段必须总是看似已经初始化(§12.4.2);如果这样的字段是非
YFOO
,那么除了包含该字段的类之外,二进制文件中的代码中不应该存在对该字段的引用。 (它将是一个类而不是一个接口,因为一个接口只有static
个字段。)该类应该有代码在实例创建期间将字段的值设置为V(§12.5)。
在§13.4.9:
如果字段是常量变量(§4.12.4),而且是
static
,则删除关键字static
或更改其值不会破坏与预先存在的二进制文件的兼容性它们不会运行,但除非重新编译,否则它们不会看到使用该字段的任何新值。[...]
在广泛分布的代码中避免“常量常量”问题的最佳方法是仅将
static
常量变量用于真正不可能改变的值。除了真正的数学常数之外,我们建议源代码使用final
常量变量。
结果是,如果您的公共库公开了任何常量变量,那么必须永远不会更改它们的值,如果您的新库版本应该与针对旧版本库编译的代码兼容。它不一定会导致错误,但现有代码可能会出现故障,因为它会对常量值的想法过时。 (如果您的新库版本需要使用它来重新编译的类,那么更改常量不会导致此问题。)
因此,使用块初始化常量可以更自由地更改其值,因为它可以防止编译器将值嵌入到其他类中。
答案 4 :(得分:2)
唯一的区别是初始化时间。
Java首先初始化成员,然后初始化静态块。
答案 5 :(得分:2)
另一个方面:考虑具有多个静态字段的情况,是的,这是一个极端情况......
正如Jon Skeet的回答所述,JLS定义了初始化的确切顺序。 但是,如果由于某种原因必须按特定顺序初始化多个静态属性,则可能需要在代码中清楚地显示初始化序列。 使用直接字段初始化时:某些代码格式化程序(和开发人员)可能会在某些时候决定对字段进行不同的排序,这将直接影响字段的初始化方式并引入不需要的效果。
顺便说一句,如果你想遵循常见的java编码约定,你应该在定义常量时使用大写字母。 (最终静态字段)。
---编辑反映Jon Skeet的评论---
答案 6 :(得分:-2)
静态块为您提供的不仅仅是简单的语句。在这个特殊情况下是同样的事情。 在构造任何实例之前,静态部分将在类加载时执行。您可以在此处调用方法并将其结果分配给静态字段。你可以在静态块中捕获异常。