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

Java静态final字段异常处理
本文探讨了Java中如何正确处理静态final字段在初始化时可能抛出的异常问题,提供了几种解决方案并分析了其原因。

原文链接:http://supermmx.org/blog/20090319_exception_in_initialization_of_static_final_field

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

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

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

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

编译的结果如下:

  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. public class StaticFinalException {
  3. public static final Test TEST_1;
  4. static {
  5. try {
  6. TEST_1 = new Test("value1");
  7. } catch ( Exception e) {
  8. }
  9. }
  10. }
  11. ...

结果是这样:

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

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

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

结果如下:

  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. public class StaticFinalException {
  3. public static final Test TEST_1 = initTest1();
  4. private static Test initTest1() {
  5. try {
  6. return new Test("value1");
  7. } catch ( Exception e) {
  8. }
  9. return null;
  10. }
  11. }
  12. class Test {
  13. public Test( String value) throws Exception {
  14. }
  15. }

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

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

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

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

开始一直想不通,编译的时候知道只有这么一条语句,不管怎么看 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. }

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值