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

Can a Java static initializer call a static method?

我可以在 Java 中从静态初始值设定项调用静态方法吗?以下是否有效并保证按照 Java 规范工作?

public class Foo {

  private final static int bar;

  private static int generateValue() {
    return 123;
  }

  static {
    bar = generateValue();
  }

}

让我感到奇怪的是,我可能希望 bargenerateValue() 中可用。我知道静态初始化块的顺序很重要,但我没有听说过静态方法声明的顺序很重要。但是在执行静态初始化程序块之前是否可以使用静态方法?

一句话-是的。这是完全合法的代码,应该 [静态] 初始化 bar123.

这里真的不需要静态初始化程序,我不会使用它。你可以做到

private final static int bar = generateValue();

即使 generateValue() 方法是在静态成员之后定义的(而且我只是尝试确定)。

在我的书中,静态初始化器仅在复杂的初始化或初始化器可以抛出异常时才需要。例如,这行不通

private final InetAddress inet = InetAddress.getByName ("some bad host name");

因为可以抛出异常。如果需要处理 if-then-else 逻辑,您还需要使用静态初始化器,可能需要一个临时变量,或者其他任何不是直接赋值的东西。

但是对于您所拥有的,静态初始化程序块是完全无关紧要的,在我看来,这不是最佳的模拟实践。

正如@Mureinik 所说,"In a word -yes. This code is perfectly legal." 我想提供一个更彻底的答案,因为在将静态初始化器与 class 方法结合使用时很容易出现问题——出现的顺序会影响class 状态,这是一个需要追踪的 讨厌的错误

A static initializer declared in a class is executed when the class is initialized. Together with any field initializers for class variables... static initializers may be used to initialize the class variables of the class -- Java Language Specification (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);
    }
}

Foo.jBar.j 的值不同,因为代码的文本顺序不同:

Foo.j = 1
Bar.j = 2

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);
    }
}

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

//Don't do this!
class Z { 
    static { i = j + 2; } //Produces a compilation error
    static int i, j;
    static { j = 4; }
}

有趣的是,方法的访问不会以这种方式检查,所以:

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);
    }
}

产生以下输出:

Foo.i = 0

发生这种情况是因为

the variable initializer for i uses the class method peek to access the value of the variable j before j has been initialized by its variable initializer, at which point it still has its default value -- JLS §8.3.2.3

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

Foo.i = 1

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

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);
    }
}

这个 peek 在初始化 i 时抛出一个 NullPointerException:

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)

Eclipse弹出这个window当上面的代码是运行:

将此与 OP 联系起来,如果 generateValue() 方法没有返回 123,而是返回其他某个静态字段(或方法)的值,则 [=22= 的值] 取决于代码的文本顺序。

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

并不总是使用文本排序。有时 JVM 会先行执行初始化。重要的是要知道何时可以进行前瞻,以及何时以文本顺序进行初始化。 JLS描述了Restrictions on the use of Fields during Initialization in §8.3.2.3(强调我自己的):

The declaration of a member needs to appear textually before it is used only if the member is [a] ...static field of a class or interface C and all of the following conditions hold:

  • The usage occurs in [a]... static variable initializer of C or in [a] ...static initializer of C.
  • The usage is not on the left hand side of an assignment.
  • The usage is via a simple name.
  • C is the innermost class or interface enclosing the usage.

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

At run time, static fields that are final and that are initialized with constant expressions (§15.28) are initialized first (§12.4.2).