简体   繁体   English

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

[英]Can a Java static initializer call a static method?

Can I call a static method from a static initializer in Java? 我可以从Java中的静态初始化程序调用静态方法吗? Is the following valid and guaranteed to work as per the Java specification? 以下是否有效并保证按照Java规范工作?

public class Foo {

  private final static int bar;

  private static int generateValue() {
    return 123;
  }

  static {
    bar = generateValue();
  }

}

What makes me wonder is that I might expect bar to be available inside generateValue() . 让我想知道的是,我可能希望bargenerateValue()可用。 I know the order of static initializer blocks is important, but I hadn't heard of the order of static method declarations being significant. 我知道静态初始化程序块的顺序很重要,但我没有听说静态方法声明的顺序很重要。 But are static methods made available before static initializer blocks are executed? 但是,在执行静态初始化程序块之前,静态方法是否可用?

As @Mureinik said, "In a word -yes. This code is perfectly legal." 正如@Mureinik所说,“总而言之 - 是的。这段代码完全合法。” I want to provide a more thorough answer because you can easily develop problems when using static initializers in conjunction with class methods--the order of appearance can affect the class state, and it's a nasty bug to track down. 我想提供一个更全面的答案,因为当将静态初始化器与类方法结合使用时,您可以轻松地产生问题 - 外观的顺序会影响类状态,这是一个令人讨厌的错误

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 与类变量的任何字段初始化器一起...静态初始化器可用于初始化类的类变量 - Java语言规范(JLS)§8.7

Initialization generally takes place in the order of appearance (called textual ordering ). 初始化通常按照出现的顺序进行(称为文本排序 )。 For example, consider the following code: 例如,请考虑以下代码:

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

The value for Foo.j and Bar.j are different because of the differences in the textual ordering of the code: Foo.jBar.j的值不同,因为代码的文本排序有所不同:

Foo.j = 1
Bar.j = 2

The OP's example executes in textual order. OP的示例以文本顺序执行。 But what if the code were rearranged, say, in reverse order: 但是,如果代码重新排列,例如,按相反的顺序,该怎么办:

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

It turns out, that this compiles without error. 事实证明,这编译没有错误。 Furthermore, the output is: Foo.bar = 123 . 此外,输出为: Foo.bar = 123 So, bar does in fact contain 123 at runtime. 因此, bar实际上在运行时包含123 However, the following code (from JLS §8.3.1.1 ) produces a compile time error because it tries to access j before j is declared: 然而,下面的代码(从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; }
}

Interestingly, accesses by methods are not checked in this way, so: 有趣的是,方法访问不会以这种方式检查,因此:

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

produces the following output: 产生以下输出:

Foo.i = 0

this happens because 这是因为

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使用类方法peek访问变量的值j之前j已由其变量初始值初始化,此时它仍然有它的默认值- JLS§8.3.2.3

If instead, i is initialized after j , then the output is 相反,如果i j之后初始化,则输出为

Foo.i = 1

And the situation gets worse when using objects instead of primitive types, as in: 使用对象而不是基本类型时情况会变得更糟,如:

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

This peek throws a NullPointerException while initializing 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 pops-up this window when the above code is run: 运行上面的代码时Eclipse会弹出这个窗口:

发生了Java异常

Tying this back to the OP, if instead of returning 123 , the generateValue() method returned the value of some other static field (or method), then the value of bar depends on the textual ordering of the code. 将此绑定回OP,如果不返回123 ,则generateValue()方法返回其他静态字段(或方法)的值,则bar的值取决于代码的文本顺序。

So, when is textual ordering important? 那么,文本排序何时重要?

Textual ordering is not always used. 不总是使用文本排序。 Sometimes the JVM looks-ahead to perform initialization. 有时JVM会预先执行初始化。 It's important to know when look-ahead is possible, and when initialization occurs in textual order. 重要的是要知道何时可以预见,以及何时以文本顺序进行初始化。 The JLS describes the Restrictions on the use of Fields during Initialization in §8.3.2.3 (emphasis my own): JLS描述了§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: 成员的声明只有在成员是[a] ...类的静态字段或接口C并且满足以下所有条件时才需要以文本方式显示:

  • The usage occurs in [a]... static variable initializer of C or in [a] ...static initializer of C. 用法发生在[a] ... C的静态变量初始化器或[a] ... 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. C是封闭用法的最内层类或接口。

One last note: constants are initialized first (per JLS §8.3.2.1): 最后一点:首先初始化常量(根据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). 在运行时,最终的静态字段和使用常量表达式(第15.28节)初始化的静态字段首先被初始化(第12.4.2节)。

In a word - yes. 总而言之 - 是的。 This is perfectly legal code, and should [statically] initialize bar to 123 . 这是完全合法的代码,应该[静态]将bar初始化为123

Static initializer is really unnecessary here and I wouldn't use it. 这里真的不需要静态初始化器,我也不会使用它。 You can do 你可以做

private final static int bar = generateValue();

even if the generateValue() method is defined after the static member (and I just tried it to be sure). 即使在静态成员之后定义了generateValue()方法(我只是尝试了它)。

In my book, static initializers are only necessary for complicated initializations or when the initializer can throw an exception. 在我的书中,静态初始化器只是复杂初始化或初始化器可以抛出异常所必需的。 For example, this isn't going to work 例如,这不起作用

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

because an exception can be thrown. 因为可以抛出异常。 You also need to use a static initializer if there's if-then-else logic to deal with, perhaps a temporary variable is needed, or anything else that isn't a straight assignment. 如果有if-then-else逻辑要处理,也需要使用静态初始化程序,可能需要临时变量,或者其他任何不是直接赋值的东西。

But for what you have here, a static initializer block is totally extraneous and, in my opinion, not a best practice to emulate. 但是对于你所拥有的东西,静态初始化程序块是完全无关的,在我看来,这不是一个模仿的最佳实践。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM