感谢原文作者:http://supermmx.org/blog/20090319_exception_in_initialization_of_static_final_field
有时候需要一些预先创建好的对象以便别的类直接使用,这些对象通常都是静态最终常量(static final),通常都是这样创建的:
-
package org.supermmx.example.misc ;
-
-
public class StaticFinalException {
-
public static final Test TEST_1 = new Test ( "value1" ) ;
-
}
-
-
class Test {
-
}
-
}
但如果在构造函数中声明有异常抛出的话,会怎么样呢?
-
package org.supermmx.example.misc ;
-
-
public class StaticFinalException {
-
public static final Test TEST_1 = new Test ( "value1" ) ;
-
}
-
-
class Test {
-
}
-
}
编译的结果如下:
-
StaticFinalException.java:4: 未报告的异常 java.lang.Exception;必须对其进行捕捉或声明以便抛出
-
public static final Test TEST_1 = new Test("value1");
-
^
-
1 错误
这种异常是必须要抓住的,显然不能直接放在赋值语句中,所以首先想到是不是可以放到静态初始化中(static intialization)去:
-
package org.supermmx.example.misc ;
-
-
public class StaticFinalException {
-
public static final Test TEST_1 ;
-
-
static {
-
try {
-
TEST_1 = new Test ( "value1" ) ;
-
}
-
}
-
}
-
...
结果是这样:
-
StaticFinalException.java:3: 可能尚未初始化变量 TEST_1
-
public class StaticFinalException {
-
^
-
1 错误
这个结果也是预料之中的,如果抛出异常的话,TEST_1
就有可能没有赋值。那么就在 catch
中设为 null
。
-
package org.supermmx.example.misc ;
-
-
public class StaticFinalException {
-
public static final Test TEST_1 ;
-
-
static {
-
try {
-
TEST_1 = new Test ( "value1" ) ;
-
TEST_1 = null ;
-
}
-
}
-
}
-
-
class Test {
-
}
-
}
结果如下:
-
StaticFinalException.java:10: 可能已指定变量 TEST_1
-
TEST_1 = null;
-
^
-
1 错误
对于 final
变量来说,它只能赋值一次,引用 Java 语言规范 4.12.4 final Variables
:
A final variable may only be assigned to once. It is a compile time error if a final variable is assigned to unless it is definitely unassigned (§16) immediately prior to the assignment.
也就是说,在一次赋值之前,这个变量必须是明确未赋值的。否则就出现上述的编译错误。但上述代码如果抛了异常以后,TEST_1
是应该没有赋值的,因为只有一条语句,为什么还会报可能已经赋值的错呢?就必须看一下在这个时候 TEST_1
的赋值状态,在 16.2.15 try Statements
小节:
# V is definitely unassigned before a catch block iff all of the following conditions hold:
* V is definitely unassigned after the try block.
...
只有满足一系列的条件时,V 在 catch
块之前才是明确未赋值的,其中一个就是 V 在 try
块之后必须是明确未赋值,try
块里面是一条赋值语句,在 16 小节
的引言中:
In all, there are four possibilities for a variable V after a statement or expression has been executed:
...
* V is not definitely assigned and is not definitely unassigned.(The rules cannot prove whether or not an assignment to V has occurred.)
...
也就是说流程分析在无法判断对 V 的赋值是否发生了,那么 V 既不是明确赋值,也不是明确未赋值。
那在我们的例子中,对于 TEST_1
的赋值是有可能 抛出异常的,不一定正常执行结束,所以它不是明确赋值,也不是明确未赋值。所以在 try
块之后、catch
块之前,它不是明确未赋值,所以编译会报错。
不过还是有解决方法,如下:
-
package org.supermmx.example.misc ;
-
-
public class StaticFinalException {
-
public static final Test TEST_1 = initTest1 ( ) ;
-
-
private static Test initTest1 ( ) {
-
try {
-
return new Test ( "value1" ) ;
-
}
-
return null ;
-
}
-
}
-
-
class Test {
-
}
-
}
不过这样有个不好的地方,就是如果有太多变量的话,看起来不好看,写起来也很费劲,代码重复也很多。
当然最简单的还是不抛异常或者抛的是运行时异常:
-
package org.supermmx.example.misc ;
-
-
public class StaticFinalException {
-
public static final Test TEST_1 = new Test ( "value1" ) ;
-
}
-
-
class Test {
-
}
-
}
当然有违初衷,如果在其他需要处理的地方没抓住异常,就可能导致错误的结果。
开始一直想不通,编译的时候知道只有这么一条语句,不管怎么看 TEST_1
都只会赋值一次,怎么还会报错呢?但看 16 章中给出的例子:
-
void unflow ( boolean flag ) {
-
final int k ;
-
if ( flag ) {
-
k = 3 ;
-
}
-
if ( ! flag ) {
-
k = 4 ; // k is not "definitely unassigned" before here
-
}
-
}
就明白了,明确赋值分析是逐行静态分析的,只要不是常量,并不考虑上下文的真正语义。