【问题标题】:Why does the Java compiler not understand this variable is always initialized?为什么Java编译器不理解这个变量总是被初始化?
【发布时间】:2012-10-25 11:49:19
【问题描述】:
class Foo{
    public static void main(String args[]){
        final int x=101;

        int y;
        if(x>100){
            y=-1;
        }
        System.out.println(y);
    }
}

Java 编译器理解 if 语句的条件始终为真,因此 y 将始终被初始化。正如预期的那样,没有编译错误。

class Bar{
    public static void main(String args[]){
        final int x;
        x=101;

        int y;      
        if(x>100){
            y=-1;
        }
        System.out.println(y);
    }
}

但是当我将 x 的声明和初始化分成两行时,编译器似乎没有得到条件始终为真并且 y 将始终被初始化。

final int x;
x=101;
byte b;
b=x;
System.out.println(b);

同样的事情发生在这里,编译器给出了精度损失错误。

final int x=101;
byte b;
b=x;
System.out.println(b);

再次,编译器可以理解 x 在 b 的范围内。

【问题讨论】:

  • 好问题。我不知道答案,但在尝试编译时查看您遇到的错误可能会有所帮助。
  • 编译器就是这么聪明。它也不会接收if(i <= Integer.MAX_VALUE)。我建议您不要编写这样的代码,因为它会造成混淆,并且编译器最好谨慎恕我直言
  • 你在两个类的末尾都缺少一个 }...你有没有尝试编译这个?
  • 当然我编译了它,当我将它从 IDE 复制到浏览器时,似乎已经删除了结束两个类的 }。
  • 不过,我想知道,有没有一个实用的设置,你不能先给 i 分配一个默认值?让你的整个程序的可编译性取决于 x 具有特定值,这听起来像是糟糕的编码风格,毕竟,最终变量的全部意义在于你多次使用某个常量时,所以你以后可以更轻松地更改它。跨度>

标签: java initialization conditional final


【解决方案1】:

作为实现可移植性目标的一部分,对于编译器应该接受什么和应该拒绝什么有一套非常具体的规则。在确定变量在使用时是否被明确分配时,这些规则既允许也只需要有限形式的流分析。

请参阅 Java 语言规范 Chapter 16. Definite Assignment

关键规则是16.2.7. if Statements, "if (e) S" 的情况。明确分配的规则扩展为:

Vif (e) S 之后赋值当且仅当 VS 之后赋值> 和 Ve 之后赋值为 false。

y 是相关的V。它在 if 语句之前未分配。它确实是在 S, y = {y=-1;} 之后分配的,但是当 x>100 为 false 时,没有任何东西使它分配。

因此 y 不是在 if 语句之后明确分配的。

更完整的流程分析将确定条件 x>100 始终为真,但 JLS 要求编译器根据这些特定规则拒绝程序。

最终变量很好。规则实际上是:-

"如果将最终变量分配给除非 在紧接在 任务。”

声明使其绝对未赋值,即使是有限流分析也可以确定 x 在赋值时仍然绝对未赋值。

【讨论】:

  • 您的原始答案指向了正确的方向,但您使用的报价与问题无关:x 在被分配之前肯定是未分配的。问题是编译器不能确定y肯定是在println语句之前赋值的。
  • 这就是我在第一段中所说的,但考虑到这个问题的受欢迎程度,我想我会扩展我的答案以更明确。
  • @PatriciaShanahan 根本原因是final int x = 101;if(x>100) 是一个常量表达式,而对于final int x; x = 101;,它不是一个常量表达式。请参阅我的答案以获得更高的精度。
【解决方案2】:

这与编译器如何确定语句是否执行有关。定义在JLS #16:

当访问其值时,每个局部变量和每个空白最终字段都必须具有明确分配的值。

在您的情况下,编译器无法确定 y 已明确分配并给您一个错误。这是因为它需要确定条件始终为真,并且只有在if 中的条件是常量表达式时才有可能。

JLS #15.28 定义常量表达式

编译时常量表达式是表示原始类型值或字符串的表达式,它不会突然完成并且仅使用以下内容组成:

  • [...]
  • 引用常量变量 (§4.12.4) 的简单名称 (§6.5.6.1)。

JLS #4.12.4常量变量定义为:

原始类型或 String 类型的变量,它是 final 并使用编译时常量表达式初始化,称为常量变量。

在您的情况下,final int x = 101; 是一个常量变量,但 final int x; x = 101; 不是。

【讨论】:

  • +1 这个答案是克服y 对原因的归属症状的最佳选择,即xif
  • 我不确定我是否同意对非最终变量(即原始变量)强制进行明确赋值的基本原理。这应该更多地在开发人员方面,而不是编译器方面。例如,默认情况下,int 变量的值是0,那么如果我们要检查方法是否返回有效值,为什么还需要明确地赋值呢?当然我们可以初始化它,但是为什么编译器知道确定值而不是依赖默认值如此重要?
  • 另外,如果创建了一个非原始变量,比如String,或者Foo,那么默认是null。如果我们试图在它为空时访问它,那么应该抛出运行时异常,因为它是空的。或者至少可以指出它作为 IDE 标记的警告。
【解决方案3】:

你对第二个代码中的变量x做了什么叫做blank final变量。如果 final 变量在声明时未初始化,则称为空白 final 变量。

许多 Java 开发人员认为最终变量的值在编译时是已知的。这并非总是如此。据说在编译时不知道空白最终变量的值。因此,您的第二个代码会给您一个编译错误。编译器可以看到你已经初始化了最终变量x,但是编译不知道它的值。所以编译器无法解析 if 语句。因此它认为变量y 没有被初始化。

您可以阅读有关 Java 最终变量的更多信息here

【讨论】:

  • 空白决赛...可能想调查一下。
  • 具体来说,JLS 的第 16.1.1 节规定了布尔常量表达式的特殊规则。用初始化器替换对 x 的赋值会将 x>100 变为 1。
猜你喜欢
  • 2023-01-10
  • 2015-05-13
  • 1970-01-01
  • 2013-10-08
  • 2011-01-22
  • 1970-01-01
  • 2012-07-18
  • 2012-08-31
  • 1970-01-01
相关资源
最近更新 更多