Java静态初始化程序可以调用静态方法吗?

Gar*_*son 6 java static-methods static-initializer

我可以从Java中的静态初始化程序调用静态方法吗?以下是否有效并保证按照Java规范工作?

public class Foo {

  private final static int bar;

  private static int generateValue() {
    return 123;
  }

  static {
    bar = generateValue();
  }

}
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,我可能期望bar在里面可用generateValue().我知道静态初始化程序块的顺序很重要,但我没有听说静态方法声明的顺序很重要.但是,在执行静态初始化程序块之前,静态方法是否可用?

Aus*_*n D 5

正如@Mureinik所说,“总之-是的。此代码完全合法。” 我想提供一个更彻底的答案,因为在将静态初始化程序与类方法结合使用时,您可以轻松地开发出问题-出现的顺序会影响类的状态,这是一个令人讨厌的错误

初始化类时,将执行在类中声明的静态初始化器。与类变量的任何字段初始化程序一起使用...静态初始化程序可用于初始化类的类变量-Java语言规范(JLS)§8.7

初始化通常按照出现的顺序(称为文本顺序)进行。例如,考虑以下代码:

class Bar {
    static int i = 1;
    static {i += 1;}
    static int j = i;
}

class Foo {
    static int i = 1;
    static int j = i;
    static {i += 1;}

    public static void main(String[] args) {
        System.out.println("Foo.j = " + Foo.j);
        System.out.println("Bar.j = " + Bar.j);
    }
}
Run Code Online (Sandbox Code Playgroud)

对于价值Foo.jBar.j的,因为在代码中的文本排序的差异是不同的:

Foo.j = 1
Bar.j = 2
Run Code Online (Sandbox Code Playgroud)

OP的示例以文本顺序执行。但是,如果代码以相反的顺序重新排列,该怎么办:

class Foo {
    static { bar = generateValue(); }                  //originally 3rd
    private static int generateValue() { return 123; } //originally 2nd
    private final static int bar;                      //originally 1st

    public static void main(String[] args) {
        System.out.println("Foo.bar = " + Foo.bar);
    }
}
Run Code Online (Sandbox Code Playgroud)

事实证明,该编译没有错误。此外,输出为:Foo.bar = 123。因此,bar实际上包含123在运行时。但是,以下代码(来自JLS§8.3.1.1)会产生编译时错误,因为它尝试jj声明之前进行访问:

//Don't do this!
class Z { 
    static { i = j + 2; } //Produces a compilation error
    static int i, j;
    static { j = 4; }
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,没有以这种方式检查方法的访问,因此:

class Foo {
    static int peek() { return j; }
    static int i = peek();
    static int j = 1;

    public static void main(String[] args) {
        System.out.println("Foo.i = " + Foo.i);
    }
}
Run Code Online (Sandbox Code Playgroud)

产生以下输出:

Foo.i = 0
Run Code Online (Sandbox Code Playgroud)

这是因为

变量初始值设定项用于i使用class方法在变量初始peek值设定项对其进行初始化j之前访问变量的值,此时j该变量初始值仍具有其默认值-JLS§8.3.2.3

如果相反,i在之后 初始化j,则输出为

Foo.i = 1
Run Code Online (Sandbox Code Playgroud)

当使用对象而不是原始类型时,情况变得更糟,如下所示:

class Foo { //Don't do this
    static int peek() { return j.hashCode(); } // NullPointerException here
    static int i = peek();
    static Object j = new Object();

    public static void main(String[] args) {
        System.out.println("Foo.i = " + Foo.i);
    }
}
Run Code Online (Sandbox Code Playgroud)

peek需要NullPointerException初始化一段时间i

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
    at TestGame.Foo.peek(Foo.java:4)
    at TestGame.Foo.<clinit>(Foo.java:5)
Run Code Online (Sandbox Code Playgroud)

运行上面的代码时,Eclipse弹出此窗口:

发生Java异常

这绑回的OP,如果不是返回123,该generateValue()方法返回一些其他的静态字段(或方法)的值,那么价值bar取决于代码的文本排序。

那么,什么时候文本排序很重要?

并非总是使用文本顺序。有时,JVM会提前执行初始化。重要的是要知道何时可以进行提前查找,以及何时以文本顺序进行初始化。JLS 在第8.3.2.3节(强调我自己)中描述了初始化期间对字段的使用限制

只有在成员是类或接口C的[a] ...静态字段且满足以下所有条件的情况下,成员的声明才需要使用文本形式出现:

  • 用法发生在C的[a] ...静态变量初始值设定项或C的[a] ...静态初始值设定项中。
  • 用法不在作业的左侧。
  • 用法是通过一个简单的名称。
  • C是包含用法的最里面的类或接口。

最后一点:常量首先被初始化(根据JLS§8.3.2.1):

在运行时,将首先初始化final并使用常量表达式初始化的静态字段(第15.28节)(第12.4.2节)。