在Java中初始化最终变量时出现问题

在Java中初始化最终变量时出现问题,java,exception,constants,final,Java,Exception,Constants,Final,我不断遇到Java中的一个问题的细微变化,它开始困扰我,我真的想不出一个合适的方法来解决它 我有一个对象属性是最终的,但是动态的。也就是说,我希望赋值后该值保持不变,但每个运行时该值可以不同。因此,我在类的开头声明了类级别变量,比如说private final FILE\u NAME。然后,在构造函数中,我给它赋值-比如说FILE_NAME=buildFileName() 当我在抛出异常的buildFileName()方法中有代码时,问题就开始了。因此,我在构造函数中尝试类似的方法: try{

我不断遇到Java中的一个问题的细微变化,它开始困扰我,我真的想不出一个合适的方法来解决它

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

当我在抛出异常的
buildFileName()
方法中有代码时,问题就开始了。因此,我在构造函数中尝试类似的方法:

try{
   FILE_NAME = buildFileName();
}
catch(Exception e){
   ...
   System.exit(1);
}
现在我有一个错误——“空白的最终字段文件名可能尚未初始化。”这就是我开始对Java的严格编译器感到有点恼火的地方。我知道这不会是一个问题,因为如果它到达捕获点,程序将退出。。。但是编译器不知道这一点,因此不允许使用此代码。如果我尝试向catch添加一个伪赋值,我会得到——“最终的字段文件名可能已经被赋值了。”我显然不能在try catch之前赋值,因为我只能赋值一次


有什么想法吗?

再想想,我想我刚刚想出了一个解决办法使用中间变量

String fileName = null;
try{
   fileName = buildFileName();
}
catch(Exception e){
   ...
   System.exit(1);
}
FILE_NAME = fileName;
不知道为什么我花了这么长时间才想到这个…

怎么样

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

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
可能是个坏主意。这会把你的单元测试搞得一团糟。

我个人只会抛出一个错误——如果你的错误流设计正确,System.exit()应该是多余的。如果抛出错误,您的程序可能不会陷入荒野…?

与OP的问题一样,我必须找到一种方法,将值分配给要从文件系统上的.properties文件中读取的最终字段,因此在发生这种情况之前,我的应用程序无法知道这些值。谢天谢地,在应用程序启动时将.properties文件的内容读入properties对象后,使用一个通用方法调用来分配值是一个万岁的过程。它还将文件读取次数限制为每次应用程序加载到内存时读取一次,只需通过代码检查属性对象当前是否为空。但是,当然,一旦指定,最终字段的值就不能更改,除非在运行时通过创建字段的修改定义来更改其“最终”状态(正如在这里的一些其他地方所讨论的,例如-skilly,但我喜欢它!)。代码示例,为简洁起见,省略了典型的运行时错误检查,例如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()允许方法的调用代码传入默认值,以便在external.Properties文件中找不到属性的键值对时使用。

谢谢:)我已经面临这个问题一段时间了,这只会让我觉得。。。应该是对这个问题的一种自动反应,就像你对这个问题的反应一样。我想你比我快了几秒钟;)这太难看了-你不想要
=”
。不,也不要
null
。它不应该被分配。@Tom Hawtin:你必须初始化
fileName
。如果您不这样做,您将得到
局部变量文件名可能尚未初始化
编译错误
null
绝对是个不错的选择。@Alexander Pogrebnyak不,你没有。如果您肯定不再读取变量,则不会。@Tom Hawtin:必须将变量初始化为NULL,因为您的
fileName()
函数必须在
系统之后
抛出错误()
。退出(1)
。对于编译器
System.exit
只是另一个函数,它需要在块中使用一个特殊的终止符。当然,您可以将
fileName=null
系统之后。退出
,而不是在声明行初始化它。顺便说一句,我确实认为您的解决方案更干净。您的意思是
private static final FILE\u NAME?@Tom Hawtin-不。为什么它应该是静态的?我同意Ryan的答案(并且投了适当的赞成票)。但我要说的是。。。如果无法初始化,是否确实要退出System.exit?也许最好的办法是让异常被抛出——无论哪种方式,如果它没有被处理,你仍然会退出,但是如果有一个处理机制,它可以被正确处理。@froadie:因为
所有的大写字母
都表示常量(又称为static final
):@froadie:没错。至少sun命名惯例规定“变量声明类常量”应该用
所有的大写字母来命名。其他字段,即使是
final
,也应该是带有小写起始字符的
camelCase
。在第一个示例中,为什么抛出新错误会使其通过编译器?为什么添加一个永远不会到达的行比为一个临时字符串指定一个默认值要简单呢?@froadie明确指定规则的详细信息在JLS中。它们非常单调乏味,但是你应该能够通过例子知道发生了什么。如果未捕获到异常,编译器需要检测变量是否被明确分配,如果捕获到异常,代码将通过异常离开,因此不需要明确分配变量。(对于
final
实例字段,有一些方法可以偷偷地查看未初始化变量的前后情况。)确定。但为什么这是一个更好的方法?永远不会到达
抛出新错误()
行,它只是为了安抚编译器。我