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();
}
}
让我感到奇怪的是,我可能希望 bar
在 generateValue()
中可用。我知道静态初始化块的顺序很重要,但我没有听说过静态方法声明的顺序很重要。但是在执行静态初始化程序块之前是否可以使用静态方法?
一句话-是的。这是完全合法的代码,应该 [静态] 初始化 bar
到 123
.
这里真的不需要静态初始化程序,我不会使用它。你可以做到
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.j
和 Bar.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).
我可以在 Java 中从静态初始值设定项调用静态方法吗?以下是否有效并保证按照 Java 规范工作?
public class Foo {
private final static int bar;
private static int generateValue() {
return 123;
}
static {
bar = generateValue();
}
}
让我感到奇怪的是,我可能希望 bar
在 generateValue()
中可用。我知道静态初始化块的顺序很重要,但我没有听说过静态方法声明的顺序很重要。但是在执行静态初始化程序块之前是否可以使用静态方法?
一句话-是的。这是完全合法的代码,应该 [静态] 初始化 bar
到 123
.
这里真的不需要静态初始化程序,我不会使用它。你可以做到
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.j
和 Bar.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 methodpeek
to access the value of the variablej
beforej
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).