如何告诉Java变量不可能为null?

时间:2019-05-15 11:32:52

标签: java null initialization

我有一个基本上像这样的程序:

boolean[] stuffNThings;
int state=1;
for(String string:list){
   switch(state){
      case 1:
         if(/*condition*/){
            // foo
            break;
         }else{
            stuffNThings=new boolean[/*size*/];
            state=2;
         }
      // intentional fallthrough
      case 2:
         // bar
         stuffNThings[0]=true;
   }
}

正如您所看到的,情况2仅在先前存在状态1并且在初始化数组后切换到状态2时才会发生。但是Eclipse和Java编译器看不到这一点,因为对于他们来说,逻辑看起来非常复杂。所以Eclipse抱怨:

  

本地变量ststNThings可能尚未初始化。”

如果我将“ boolean[] stuffNThings;”更改为“ boolean[] stuffNThings=null;”,它将切换到以下错误消息:

  

可能的空指针访问:在该位置,变量ststNThings可能为空。

我也不能在顶部初始化它,因为数组的大小仅在状态1的最终循环之后确定。

Java认为那里的数组可以为null,但我知道不能。有什么办法可以告诉Java吗?还是我肯定被迫对它进行无用的null检查?加上它会使代码更难理解,因为看起来好像在某些情况下该值实际上并未设置为true

4 个答案:

答案 0 :(得分:5)

  

Java认为那里的数组可以为null,但我知道不能。

严格来说,Java认为该变量可以未初始化。如果未明确初始化,则该值不应是 observable

(该变量是默默地初始化为null还是保留为 indeterminate 状态,这是实现细节。要点是,该语言指出,不应让您看到值。)

但是无论如何,解决方案是将其初始化为null。这是多余的,但是没有办法告诉Java“只要相信我,它就会被初始化”。


在获取“潜在的空指针访问”消息的版本中:

  1. 这是警告,不是错误。
  2. 您可以忽略或取消警告。 (如果您的正确性分析有误,那么您可能会得到NPE。但这是您的选择。)
  3. 您可以使用编译器开关关闭某些或所有警告。
  4. 您可以使用@SuppressWarnings注释禁止显示特定警告:

    • 对于Eclipse,请使用@SuppressWarnings("null")
    • 对于Android,请使用@SuppressWarnings("ConstantConditions")

      不幸的是,警告标签尚未完全标准化。但是,编译器应该静默忽略@SuppressWarnings来表示它无法识别的警告标记。

  5. 您也许可以重组代码。

在您的示例中,代码使用了switch drop through。人们很少这样做,因为它导致难以理解的代码。因此,您会发现涉及直接插入的极端情况示例,使编译器得到NPE警告有点错误,我并不感到惊讶。

无论哪种方式,您都可以通过重组代码轻松地避免进行直接插入的需要。将case 2:情况下的代码复制到case 1:情况下。固定。继续前进。


请注意,“可能未初始化”错误不是Java编译器“愚蠢”。 JLS的一整章都是关于“确定分配”的规则等等。不允许Java编译器精明,因为这意味着相同的Java代码合法还是不合法,具体取决于编译器的实现。这将不利于代码的可移植性。

这里实际上是一种语言设计折衷方案。该语言使您无法使用(确实)未初始化的变量。但是要做到这一点,“笨拙”的编译器有时必须使您停止使用(聪明的程序员)知道将要初始化的变量……因为规则认为应该如此。

(替代方法更糟:要么在编译时检查未初始化的变量,否则在无法预测的地方导致硬崩溃,要么检查对于不同的编译器而言是不同的。)

答案 1 :(得分:0)

一个独特的非答案:当代码“太”复杂以至于IDE / java编译器没有“看到”它时,这很好地表明您的代码反正太复杂了 。至少对我来说,你说的不清楚。我不得不反复阅读和说服自己,以使自己确信问题中的陈述是正确的。

您在for的开关中有一个if。干净的代码和“单层抽象”将告诉您:这不是一个好的起点。

查看您的代码。您所拥有的是伪装的状态机。问问自己,是否有必要进行大规模重构,例如将其转变为某种 explicit 状态机。

另一个不太麻烦的想法:使用List而不是数组。然后,您可以简单地创建一个空列表,并根据需要添加元素。

答案 2 :(得分:0)

在不考虑Eclipse抱怨的情况下尝试执行代码后,我注意到它确实可以毫无问题地运行。显然,尽管这并不重要,但这只是将警告设置为“错误”级别。
有一个“配置问题严重性”按钮,因此我将“潜在的空指针访问”的严重性设置为“警告”(并相应地调整了其他一些级别)。现在Eclipse只是将其标记为警告,并且无需抱怨即可执行代码。

答案 3 :(得分:0)

更容易理解的是:

    boolean[] stuffNThings;
    boolean initialized = false;
    for (String string: list) {
        if (!initialized) {
            if (!/*condition*/) {
                stuffNThings = new boolean[/*size*/];
                initailized = true;
            }
        }
        if (initialized) {
            // bar
            stuffNThings[0] = true;
        }
    }

两个循环,一个循环用于初始化,另一个循环用于玩游戏。

进行流量分析更容易(与具有穿透功能的开关相比)。

此外,也可以使用boolean[]代替BitSet(因为它的大小不固定为数组)。

BitSet stuffNThings = new BitSet(/*max size*/);