静态最终域初始化时抛异常的问题

有时候需要一些预先创建好的对象以便别的类直接使用,这些对象通常都是静态最终常量(static final),通常都是这样创建的:

  1. package org.supermmx.example.misc;
  2.  
  3. public class StaticFinalException {
  4.     public static final Test TEST_1 = new Test("value1");
  5. }
  6.  
  7. class Test {
  8.     public Test(String value) {
  9.     }
  10. }

但如果在构造函数中声明有异常抛出的话,会怎么样呢?

  1. package org.supermmx.example.misc;
  2.  
  3. public class StaticFinalException {
  4.     public static final Test TEST_1 = new Test("value1");
  5. }
  6.  
  7. class Test {
  8.     public Test(String value) throws Exception {
  9.     }
  10. }

编译的结果如下:

  1. StaticFinalException.java:4: 未报告的异常 java.lang.Exception;必须对其进行捕捉或声明以便抛出
  2.     public static final Test TEST_1 = new Test("value1");
  3.                                       ^
  4. 1 错误

这种异常是必须要抓住的,显然不能直接放在赋值语句中,所以首先想到是不是可以放到静态初始化中(static intialization)去:

  1. package org.supermmx.example.misc;
  2.  
  3. public class StaticFinalException {
  4.     public static final Test TEST_1;
  5.  
  6.     static {
  7.         try {
  8.             TEST_1 = new Test("value1");
  9.         } catch (Exception e) {
  10.         }
  11.     }
  12. }
  13. ...

结果是这样:

  1. StaticFinalException.java:3: 可能尚未初始化变量 TEST_1
  2. public class StaticFinalException {
  3.        ^
  4. 1 错误

这个结果也是预料之中的,如果抛出异常的话,TEST_1 就有可能没有赋值。那么就在 catch 中设为 null

  1. package org.supermmx.example.misc;
  2.  
  3. public class StaticFinalException {
  4.     public static final Test TEST_1;
  5.  
  6.     static {
  7.         try {
  8.             TEST_1 = new Test("value1");
  9.         } catch (Exception e) {
  10.             TEST_1 = null;
  11.         }
  12.     }
  13. }
  14.  
  15. class Test {
  16.     public Test(String value) throws Exception {
  17.     }
  18. }

结果如下:

  1. StaticFinalException.java:10: 可能已指定变量 TEST_1
  2.             TEST_1 = null;
  3.             ^
  4. 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 块之前,它不是明确未赋值,所以编译会报错。

不过还是有解决方法,如下:

  1. package org.supermmx.example.misc;
  2.  
  3. public class StaticFinalException {
  4.     public static final Test TEST_1 = initTest1();
  5.  
  6.     private static Test initTest1() {
  7.         try {
  8.             return new Test("value1");
  9.         } catch (Exception e) {
  10.         }
  11.         return null;
  12.     }
  13. }
  14.  
  15. class Test {
  16.     public Test(String value) throws Exception {
  17.     }
  18. }

不过这样有个不好的地方,就是如果有太多变量的话,看起来不好看,写起来也很费劲,代码重复也很多。

当然最简单的还是不抛异常或者抛的是运行时异常:

  1. package org.supermmx.example.misc;
  2.  
  3. public class StaticFinalException {
  4.     public static final Test TEST_1 = new Test("value1");
  5. }
  6.  
  7. class Test {
  8.     public Test(String value) throws RuntimeException {
  9.     }
  10. }

当然有违初衷,如果在其他需要处理的地方没抓住异常,就可能导致错误的结果。

开始一直想不通,编译的时候知道只有这么一条语句,不管怎么看 TEST_1 都只会赋值一次,怎么还会报错呢?但看 16 章中给出的例子:

  1. void unflow(boolean flag) {
  2.         final int k;
  3.         if (flag) {
  4.                 k = 3;
  5.                 System.out.println(k);
  6.         }
  7.         if (!flag) {
  8.                 k = 4;  // k is not "definitely unassigned" before here
  9.                 System.out.println(k);
  10.         }
  11. }

就明白了,明确赋值分析是逐行静态分析的,只要不是常量,并不考虑上下文的真正语义。

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote>
  • You can use BBCode tags in the text.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. Beside the tag style "<foo>" it is also possible to use "[foo]".
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
"ewacope abima ulu umiy osu ajofa edezewa zapogox"