静态修饰符和静态块之间的区别

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); 所有初始化程序(无论它们是字段初始化程序还是静态初始化程序)都以文本顺序运行.

  • @FabioMarano在java 7中是可能的 (7认同)
  • @FabioMarano:你可以使用字符串常量.正如Fast Snail所说,这是在Java 7中引入的...... (4认同)
  • @AnkitLamba - 您应该在谷歌上谷歌*Jon Skeet事实*并点击第一个链接,将您重定向到元.对不起*Jon*,他们必须知道你的秘密:P (4认同)
  • @TheLostMind:是的,但他们*也*出现在这些领域.因此,如果你有一个静态初始化器,它出现在*之前*一个用编译时常量初始化的字段,并且你使用反射来访问该字段,你仍然会在字段中看到常量值.(我刚检查过.) (4认同)
  • 为了更精确一点,在静态初始化代码运行之前,JVM初始化`static final`变量中保存的编译时常量,而非`静态``final`字段也可能保存编译时常量但是由构造函数初始化,如普通实例字段.但是,您需要反射来注意区别,因为任何对此类字段的普通引用都将在编译时被常量值替换... (3认同)

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情况下.


Boa*_*ann 8

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)

在反汇编中要注意的事情,它告诉你与语法糖之间的差异XY运行更深层次:

  • 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常量变量.

结果是,如果您的公共库公开任何常量变量,那么如果您的新库版本应该与针对旧版本库编译的代码兼容,则必须永远不要更改它们的值.它不一定会导致错误,但现有代码可能会出现故障,因为它会对常量值的想法过时.(如果您的新库版本需要使用它来重新编译的类,那么更改常量不会导致此问题.)

因此,使用块初始化常量可以更自由地更改其值,因为它可以防止编译器将值嵌入到其他类中.