Java中初始化final变量时遇到的问题

21

我在Java中遇到了一个问题的轻微变化,这让我很苦恼,我无法想出一个合适的方法来解决它。

我有一个对象属性是final但是动态的。也就是说,我希望一旦分配后,该值保持不变,但是每次运行时该值可以不同。因此,我在类的开头声明类级变量 - 比如 private final FILE_NAME;。 然后,在构造函数中,我给它分配一个值 - 比如 FILE_NAME = buildFileName();

问题开始于在buildFileName()方法中存在抛出异常的代码。所以我在构造函数中尝试像这样做:

try{
   FILE_NAME = buildFileName();
}
catch(Exception e){
   ...
   System.exit(1);
}

现在我遇到了一个错误 - "The blank final field FILE_NAME may not have been initialized."(空白终值FILE_NAME可能未初始化)。这时我开始对Java的严格编译器感到有点烦恼。我知道这不会成为问题,因为如果程序进入catch块,程序就会退出...... 但编译器并不知道这一点,所以不允许该代码。如果我尝试在catch块中添加虚拟赋值语句,则会得到"The final field FILE_NAME may already have been assigned."(最终字段FILE_NAME可能已经被分配)。很明显,我不能在try-catch之前分配默认值,因为我只能给它分配一次。

有什么建议吗...?


你是指 private static final FILE_NAME; 吗? - Tom Hawtin - tackline
2
@Tom Hawtin - 不需要静态的吧? - froadie
我同意Ryan的答案(并适当地投了赞成票)。不过有一点我要说的是……如果你不能初始化,你真的想用System.exit吗?也许最好的想法是让异常被抛出——无论如何,如果它没有被处理,你仍然会退出,但如果有一个处理机制,它可以得到适当的处理。 - corsiKa
1
@froadie:因为ALL_CAPS表示常量(也称为static final): http://java.sun.com/docs/codeconv/html/CodeConventions.doc8.html#367 - Joachim Sauer
@Joachim Sauer - 这是一个常量,但它是对象级别的常量...每个对象都可以有自己的文件名,但一旦设置就不能更改。 - froadie
1
@froadie:没错。至少Sun的命名规范说,“声明为类常量的变量”应该用ALL_CAPS命名。其他字段,即使是final,也应该使用以小写字母开头的camelCase - Joachim Sauer
5个回答

20

怎么样?

String tempName = null;
try{
   tempName = buildFileName();
}
catch(Exception e){
   ...
   System.exit(1);
}
FILE_NAME = tempName;

谢谢 :) 我一直面临这个问题,但直到现在才想到...这应该是对于你来说已经成为了自动反应解决问题的方法。 - froadie
嘿,是的。我想你比我快了几秒钟 ;) - Ryan Elkins

7

无论是

try {
   FILE_NAME = buildFileName();
} catch (Exception e){
   ...
   System.exit(1);
   throw new Error();
}

或者有些人喜欢:

private static final String FILE_NAME = fileName();

private static String fileName() {
    try {
        return buildFileName();
    } catch (Exception e){
        ...
        System.exit(1);
        throw new Error();
    }
}

但在静态初始化器中调用System.exit可能是一个坏主意。它会破坏您的单元测试。


在第一个例子中,为什么抛出新错误会使其通过编译器?为什么添加一行永远不会被执行的代码比给临时字符串赋一个默认值更加优雅? - froadie
@froadie 定义赋值规则的详细信息在JLS中。它们非常繁琐,但您应该能够通过示例知道正在发生什么。如果未捕获异常,则编译器需要检测变量是否已经被定义赋值,如果捕获了异常,则代码通过异常离开,因此不需要明确分配变量。(对于final实例字段,有一些诡计可以在之前或之后查看未初始化的变量。) - Tom Hawtin - tackline
好的,但这种方法为什么更好呢?throw new Error() 这一行永远不会被执行,只是为了让编译器满意。我没有看到这个答案相比我发布的和 Ryan Elkin 的答案有什么优势 - 如果你认为我漏掉了什么,请告诉我。 - froadie
@froadie 我认为这样更好,因为代码更简洁(1行而不是2行),而且噪音只限于错误处理代码。 - ILMTitan

4

仔细想了想,我觉得我刚刚想到了一个解决方案!- 使用一个中间变量。

String fileName = null;
try{
   fileName = buildFileName();
}
catch(Exception e){
   ...
   System.exit(1);
}
FILE_NAME = fileName;

不知道为什么我花了这么长时间才想到这个...


那很丑陋 - 你不想要 = "" - Tom Hawtin - tackline
1
不,也不是 null。它不应该被赋值。 - Tom Hawtin - tackline
1
@Tom Hawtin: 你必须将fileName初始化为某些内容。如果不这样做,你会得到局部变量fileName可能未被初始化的编译错误。null绝对是一个好的选择。 - Alexander Pogrebnyak
1
@Alexander Pogrebnyak 不需要。除非你确定不会再次读取该变量。 - Tom Hawtin - tackline
1
@Tom Hawtin:必须将变量初始化为NULL,对于与您的fileName()函数相同的令牌,必须在System.exit(1)之后throw Error()。 对于编译器而言,System.exit只是另一个函数,并且它需要块中的特殊终止符。 当然,您可以在System.exit之后放置fileName = null;,而不是在声明行处进行初始化。 顺便说一句,我认为您的解决方案更清晰。 - Alexander Pogrebnyak
显示剩余2条评论

1
我个人会直接抛出一个错误 -- 如果你的错误流程被正确设计,System.exit() 应该是多余的。假设当抛出一个错误时,你的程序不会继续执行下去...?

这甚至没有尝试回答原帖作者的实际问题。 - arkon

0

与OP的问题类似,我必须找到一种方法来为最终字段分配值,以从文件系统中的.properties文件中读取,因此在发生这种情况之前,我的应用程序无法知道这些值。使用通用方法调用,在应用程序启动时将.properties文件的内容读入Properties对象后分配值是一个幸运的尝试。它还通过代码检查Properties对象当前是否为空来限制了文件需要读取的次数,仅在应用程序加载到内存中时进行一次。但当然,一旦被分配,最终字段的值就不能被改变,除非通过在运行时操作字段的修改定义来改变其“final”状态(如在SO的其他地方讨论的那样,例如https://dev59.com/TXA75IYBdhLWcg3wbofM#3301720 - 狡猾,但我喜欢它!)。代码示例,省略了诸如NPE的典型运行时错误检查,以简洁为重:

import java.util.Properties;

public class MyConstants {

  private static Properties props; // declared, not initialized,
                                   // so it can still be set to
                                   // an object reference.

  public static String MY_STRING = getProperty("prop1name", "defaultval1");
  public static int MY_INT = Integer.parseInt(getProperty("prop2name", "1"));
  // more fields...

  private static String getProperty(String name, String dflt) {
   if ( props == null ) {
     readProperties();
   }
   return props.getProperty(name, dflt);
  } 

  private static void readProperties() {
     props = new Properties(); // Use your fave way to read
                      // props from the file system; a permutation
                      // of Properties.load(...) worked for me.
  } 

  // Testing...
  public static void main(String[] args) {
      System.out.println(MY_STRING);
      System.out.println(MY_INT);
  }

}

这使您可以将属性外部化以便读入应用程序,同时仍将用于保存其值的字段标记为“final”。它还允许您保证最终字段值的返回值,因为Properties类中的getProperty()方法允许调用代码传递默认值以在未在外部.properties文件中找到属性的键值对时使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接