Fab*_*ano 83 java static modifier
有人向我解释了以下两个陈述之间的区别吗?
阿static final由初始化的变量static的代码块:
private static final String foo;
static { foo = "foo"; }
Run Code Online (Sandbox Code Playgroud)
阿static final通过分配初始化的变量:
private static final String foo = "foo";
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 100
在这个例子中,有一个细微的区别 - 在你的第一个例子中,foo不确定是编译时常量,所以它不能用作switch块中的情况(并且不会内联到其他代码中); 在你的第二个例子中,它是.例如:
switch (args[0]) {
case foo:
System.out.println("Yes");
break;
}
Run Code Online (Sandbox Code Playgroud)
当foo被认为是一个常量表达式时,这是有效的,但当它只是一个静态的最终变量时,它是有效的.
但是,当您有更复杂的初始化代码时,通常会使用静态初始化程序块- 例如填充集合.
的定时进行初始化中描述JLS 12.4.2 ; 任何被认为是编译时常量的静态最终字段首先被初始化(步骤6)并且初始化器被稍后运行(步骤9); 所有初始化程序(无论它们是字段初始化程序还是静态初始化程序)都以文本顺序运行.
The*_*ind 34
private static final String foo;
static { foo ="foo";}
Run Code Online (Sandbox Code Playgroud)
加载类并运行静态初始化程序时,foo将初始化值.
private static final String foo = "foo";
Run Code Online (Sandbox Code Playgroud)
这里,值foo将是编译时常量.因此,实际上"foo"将作为字节码本身的一部分提供.
小智 9
在IInd情况下,foo的值是早期绑定,即编译器识别并将值foo赋值给变量FOO,这些变量无法更改,并且这将与字节码本身分开使用.
private static final String FOO = "foo";
Run Code Online (Sandbox Code Playgroud)
并且在Ist的情况下,foo初始化的值只是在类加载之后初始化为在分配实例变量之前的第一个赋值,在这里你也可以捕获异常或静态字段可以通过调用静态块中的静态方法来分配.
private static final String FOO;
static { FOO ="foo";}
Run Code Online (Sandbox Code Playgroud)
因此,只要有条件到达时编译器必须必须识别变量foo 的值,条件II就可以工作,例如大小写的情况:在switch情况下.
JLS描述了它所谓的常量变量的一些特殊行为,它们是用常量表达式或原始类型初始化的final变量(无论是否static)String.
常量变量在二进制兼容性方面有很大的不同:就编译器而言,常量变量的值成为类API的一部分.
一个例子:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这里,XFOO是一个"常数变量"而YFOO不是,但它们在其他方面是等价的.班级Z打印出每一个.编译这些类,然后用它们反汇编javap -v X Y Z,这里是输出:
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
}
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)
在反汇编中要注意的事情,它告诉你与语法糖之间的差异X和Y运行更深层次:
XFOO有一个ConstantValue属性,表示它的值是编译时常量.而YFOO不是,并使用static带有putstatic指令的块在运行时初始化值.
该String常数"xfoo"已成为类的一部分Z的常量池,但"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文件,则类Z无法使用a初始化ClassNotFoundException: Y.
如果使用javadoc生成类的文档,则"常量字段值"页面将记录值XFOO,但不记录值YFOO.
JLS描述了常量变量对§13.1.3中编译的类文件的上述影响:
必须在编译时将对作为常量变量(§4.12.4)的字段的引用解析为由常量变量的初始值设定项表示的值V.
如果这样的字段是
static,那么在二进制文件的代码中不应该存在对该字段的引用,包括声明该字段的类或接口.这样的字段必须总是看似已经初始化(§12.4.2); 必须永远不要观察该字段的默认初始值(如果不同于V).如果这样的字段是非字段
static,那么除了包含该字段的类之外,二进制文件中的代码中不应该存在对该字段的引用.(它将是一个类而不是一个接口,因为接口只有static字段.)该类应该有代码在实例创建期间将字段的值设置为V(第12.5节).
在§13.4.9中:
如果一个字段是一个常量变量(§4.12.4),而且是
static,那么删除该关键字final或更改其值不会破坏与预先存在的二进制文件的兼容性,导致它们不能运行,但它们不会看到任何新值除非重新编译,否则使用该字段.[...]
在广泛分布的代码中避免"常量常量"问题的最佳方法是
static仅将常量变量用于真正不可能改变的值.除了真正的数学常数之外,我们建议源代码非常节省地使用static常量变量.
结果是,如果您的公共库公开任何常量变量,那么如果您的新库版本应该与针对旧版本库编译的代码兼容,则必须永远不要更改它们的值.它不一定会导致错误,但现有代码可能会出现故障,因为它会对常量值的想法过时.(如果您的新库版本需要使用它来重新编译的类,那么更改常量不会导致此问题.)
因此,使用块初始化常量可以更自由地更改其值,因为它可以防止编译器将值嵌入到其他类中.