【问题标题】:Can a Java static initializer call a static method?Java 静态初始化器可以调用静态方法吗?
【发布时间】:2016-06-28 21:04:23
【问题描述】:

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

public class Foo {

  private final static int bar;

  private static int generateValue() {
    return 123;
  }

  static {
    bar = generateValue();
  }

}

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

【问题讨论】:

  • 是的。我不知道它是否保证可以工作,您的代码必须没有缺陷;并且 JVM 必须没有缺陷(以及您的主机操作系统和计算机)。写private final static int bar = generateValue(); 也是合法

标签: java static-methods static-initializer


【解决方案1】:

正如@Mureinik 所说,“总之——是的。这个代码是完全合法的。”我想提供一个更彻底的答案,因为在将静态初始化程序与类方法结合使用时很容易出现问题——出现的顺序会影响类状态,这是一个讨厌的错误 .

类中声明的静态初始化器在类初始化时执行。与类变量的任何字段初始化器一起...静态初始化器可用于初始化类的类变量 -- 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

这是因为

i 的变量初始化器使用类方法 peekj 被其变量初始化器初始化之前访问变量 j 的值,此时它仍然具有其默认值 -- 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会弹出这个窗口:

将其与 OP 联系起来,如果 generateValue() 方法不是返回 123,而是返回某个其他静态字段(或方法)的值,那么 bar 的值取决于代码。

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

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

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

  • 在 [a]... C 的静态变量初始化器或 [a] ...C 的静态初始化器中使用。
  • 用法不在作业的左侧。
  • 用法是通过一个简单的名称。
  • C 是包含用法的最内层类或接口。

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

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

【讨论】:

  • 我不明白这是怎么工作的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); } }是因为它在设计时是java语言的缺陷吗?
  • @sofs1 这是 Java 的一个特性,而不是缺陷。它的动机是维护文本排序有时可能是一项繁重的任务。在实践中,我很少遇到文本排序问题,但开发人员应该注意上面突出显示的边缘情况,因为它们引入的错误特别阴险
【解决方案2】:

总之——是的。这是完全合法的代码,应该 [静态地] 将 bar 初始化为 123

【讨论】:

  • 我假设 generateValue() 应该返回合法的东西,而不是硬编码。
【解决方案3】:

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

private final static int bar = generateValue();

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2012-03-30
    • 1970-01-01
    • 1970-01-01
    • 2012-01-01
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-27
    相关资源
    最近更新 更多