【问题标题】:Problems initializing a final variable in Java在 Java 中初始化最终变量的问题
【发布时间】:2011-02-15 22:58:01
【问题描述】:

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

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

当我在buildFileName() 方法中有引发异常的代码时,问题就开始了。所以我在构造函数中尝试这样的事情:

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

现在我有一个错误 - “空白的最终字段 FILE_NAME 可能尚未初始化。”这就是我开始对 Java 的严格编译器有点恼火的地方。我知道这不会是一个问题,因为如果它被捕获,程序将退出......但编译器不知道这一点,因此不允许此代码。如果我尝试向 catch 添加一个虚拟分配,我会得到 - “最终字段 FILE_NAME 可能已经被分配。”我显然不能在 try-catch 之前分配默认值,因为我只能分配一次。

有什么想法吗...?

【问题讨论】:

  • 你的意思是private static final FILE_NAME;
  • @Tom Hawtin - 没有。为什么它应该是静态的?
  • 我同意 Ryan 的回答(并适当投票)。我要说的一件事是......如果你不能初始化,你真的想要 System.exit 吗?也许最好的办法是让异常被抛出——不管怎样,如果它没有被处理,你仍然会退出,但是如果有一个处理机制,它可以被妥善处理。
  • @froadie:因为ALL_CAPS 表示常量(又名static final):java.sun.com/docs/codeconv/html/CodeConventions.doc8.html#367
  • @froadie:完全正确。至少 sun 命名约定说“变量声明的类常量”应该用ALL_CAPS 命名。其他字段,即使是final,也应该是camelCase,并带有一个小写的起始字符。

标签: java exception constants final


【解决方案1】:

再想一想,我想我只是想出了一个解决方案! - 使用中间变量。

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

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

【讨论】:

  • 这很丑 - 你不想要= ""
  • 不,也不是null。它不应该被分配。
  • @Tom Hawtin:您必须将fileName 初始化为某些东西。如果你不这样做,你会得到The local variable fileName may not have been initialized 编译错误。 null绝对是个不错的选择。
  • @Alexander Pogrebnyak 不,你没有。如果您确定不再读取该变量,则不会。
  • @Tom Hawtin:必须将变量初始化为 NULL,因为您的 fileName() 函数在 System.exit( 1) 之后必须为 throw Error( )。对于编译器System.exit 只是另一个函数,它需要在块中使用特殊的终止符。当然,您可以将fileName = null; 放在System.exit 之后,而不是在声明行初始化它。顺便说一句,我确实认为您的解决方案更清洁。
【解决方案2】:

怎么样

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

【讨论】:

  • 谢谢 :) 我遇到这个问题已经有一段时间了,这只是打击了我......应该是对这个问题的自动反应,因为它似乎与你有关
  • 嘿,是的。我想你在几秒钟内就打败了我;)
【解决方案3】:

要么

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 明确分配规则的详细信息在 JLS 中。它们非常乏味,但是您应该能够通过示例知道发生了什么。如果没有捕获到异常,编译器需要检测该变量是否已明确分配,如果捕获到异常,则代码通过异常离开,因此不需要明确分配该变量。 (对于final 实例字段,有一些偷偷摸摸的方法可以在之前或之后查看未初始化的变量。)
  • 好的。但为什么这是一个更好的方法? throw new Error() 行永远不会到达,只是为了安抚编译器。我看不出这个答案比我发布的答案和 Ryan Elkin 的答案有什么优势 - 如果您认为我遗漏了什么,请告诉我
  • @froadie 我认为它更好,因为噪音更少(1 行对 2 行)并且噪音仅限于错误处理代码。
【解决方案4】:

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

【讨论】:

  • -1 这甚至没有尝试回答 OP 的实际问题。
【解决方案5】:

与 OP 的问题一样,我必须能够找到一种方法将值分配给要从文件系统上的 .properties 文件中读取的最终字段,因此我的值无法知道应用程序,直到发生这种情况。在应用程序启动时将 .properties 文件的内容读入 Properties 对象后,使用通用方法调用来分配值是一个 Hail Mary 通行证,谢天谢地。它也限制了编号。每次应用程序加载到内存中时,文件必须被读取一次,只需通过代码检查来查看 Properties 对象当前是否为空。但是当然,一旦分配,最终字段的值就不能改变,除非通过在运行时操纵字段的修改定义来改变其“最终”状态(如在 SO 上的其他一些地方讨论的那样,例如https://stackoverflow.com/a/3301720/1216686 - 偷偷摸摸,但是我喜欢它!)。代码示例,为简洁起见省略了典型的运行时错误检查,例如 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);
  }

}

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 2016-10-02
    相关资源
    最近更新 更多