我可以从Java中的静态初始化程序调用静态方法吗?以下是否有效并保证按照Java规范工作?
public class Foo {
private final static int bar;
private static int generateValue() {
return 123;
}
static {
bar = generateValue();
}
}
让我想知道的是,我希望bar
可以在generateValue()
内使用new Integer(primitive);
。我知道静态初始化程序块的顺序很重要,但我没有听说静态方法声明的顺序很重要。但是,在执行静态初始化程序块之前,静态方法是否可用?
答案 0 :(得分:5)
正如@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.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
这是因为
i
的变量初始值设定项使用类方法peek
在变量初始化程序初始化j
之前访问变量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
在初始化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)
当运行上面的代码时,Eclipse会弹出此窗口:
将此绑定回OP,如果不是返回123
,generateValue()
方法返回其他静态字段(或方法)的值,则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节)。
答案 1 :(得分:3)
总之 - 是的。这是完全合法的代码,应该[静态]初始化bar
到123
。
答案 2 :(得分:1)
这里真的不需要静态初始化程序,我也不会使用它。你可以做到
private final static int bar = generateValue();
即使在静态成员之后定义了generateValue()方法(我只是尝试了它)。
在我的书中,静态初始化程序仅在复杂的初始化时或初始化程序可以抛出异常时才需要。例如,这不会起作用
private final InetAddress inet = InetAddress.getByName ("some bad host name");
因为可以抛出异常。如果需要处理if-then-else逻辑,可能还需要使用静态初始化程序,可能需要临时变量,或者其他任何不是直接赋值的东西。
但是对于你在这里所拥有的东西,静态初始化程序块是完全无关的,在我看来,这不是一个模仿的最佳实践。