使用未初始化的最终字段 - 有/没有'这个'。预选赛

时间:2012-12-13 16:44:30

标签: java

有人可以向我解释为什么以下两个样本中的第一个编译,而第二个没有?注意唯一的区别是第一个明确地使用'.this'限定对x的引用,而第二个没有。在这两种情况下,显然都会尝试在初始化之前使用最终字段x。

我原以为两个样本都会被完全平等对待,导致两者都出现编译错误。

1)

public class Foo {
    private final int x;
    private Foo() {
        int y = 2 * this.x;
        x = 5;
    }
}

2)

public class Foo {
    private final int x;
    private Foo() {
        int y = 2 * x;
        x = 5;
    }
}

4 个答案:

答案 0 :(得分:39)

经过一系列的规范阅读和思考后,我得出结论:

在Java 5或Java 6编译器中,这是正确的行为。 Chapter 16 "Definite Assignment of The Java Language Specification, Third Edition说:

  

每个本地变量(§14.4)和每个空白final (§4.12.4)字段(§8.3.1.2)在对其值进行任何访问时都必须具有明确分配的值。对值的访问包括变量的简单名称出现在表达式中的任何位置,除了作为简单赋值运算符=的左侧操作数。

(强调我的)。因此,在2 * this.x表达式中,this.x部分被视为“[x的值的访问权限”(因此不受明确赋值的规则),因为this.x不是实例变量x的简单名称。 (注意当发生明确赋值时的规则,在上述引用文本之后的段落中, 允许this.x = 3之类的内容,并认为此后明确分配x;它只是不计入this.x的访问规则。)请注意,在这种情况下,this.x的值将为零,每§17.5.2

在Java 7编译器中,这是一个编译器错误,但是可以理解。 Chapter 16 "Definite Assignment" of the Java Language Specification, Java 7 SE Edition说:

  

每个局部变量(§14.4)和每个空白final字段(§4.12.4§8.3.1.2)必须具有明确分配的值访问其价值。

     

对其值的访问权限包含变量的简单名称(或者,对于字段,由this限定的字段的简单名称)出现在表达式中的任何位置除外作为简单赋值运算符=§15.26.1)的左手操作数。

(强调我的)。所以在表达式2 * this.x中,this.x部分被视为“访问[x的'值',而应该给出编译错误。

但是你没有问 (在一些编译器中)。这必然是推测性的,但我会做两个猜测:

  1. 大多数Java 7编译器都是通过修改Java 6编译器编写的。一些编译器编写者可能没有注意到这种变化。此外,许多Java-7编译器和IDE仍然支持Java 6,并且一些编译器编写者可能没有动机去专门拒绝他们在Java-6模式下接受的Java-7模式。
  2. 新的Java 7行为奇怪地不一致。仍然允许(false ? null : this).x之类的东西,就此而言,即使(this).x仍然允许;它只是特定的令牌序列this.加上受此更改影响的字段名称。当然,这种不一致性已存在于赋值语句的左侧(我们可以写this.x = 3,但不能写(this).x = 3),但这更容易理解:它接受this.x = 3为特别允许的禁止施工obj.x = 3的案例。允许这样做是有道理的。但我不认为拒绝2 * this.x作为特别禁止的构造2 * obj.x的禁止案例是有意义的,因为(1)这个特殊的禁止案例很容易通过添加括号来解决, (2)在该语言的先前版本中允许使用此特殊禁止案例,并且(3)我们仍需要特殊规则,其中final字段具有其默认值(例如0表示{{1} }})直到它们被初始化,因为像int这样的情况,并且因为像(this).x这样的情况this.foo()是访问foo()的方法。因此,一些编译器编写者可能没有动力去做出这种不一致的改变。
  3. 这些都是令人惊讶的 - 我假设编译器编写者有关于规范的每一次更改的详细信息,根据我的经验,Java编译器通常非常适合完全遵守规范(不像某些语言,每个编译器有自己的方言) - 但是,好吧,发生了某事,以上是我唯一的两个猜测。

答案 1 :(得分:2)

在构造函数中使用this时,编译器将 x视为this 对象的成员属性(默认初始化)。由于xint,因此默认使用0进行初始化。这使得编译器很高兴,并且它在运行时也能正常工作。

当你不使用this时,编译器在词法分析中直接使用x声明,因此它抱怨它的初始化(编译时现象)。

所以它是this的定义,它使得编译器在编译中的词法分析期间将x分析为对象的成员变量与直接属性,并导致不同的编译行为。

  

当用作主表达式时,关键字this表示一个值,该值是对调用实例方法的对象(第15.12节)或对正在构造的对象的引用。

答案 2 :(得分:-1)

我认为编译器估计编写this.x意味着'this'存在,因此调用了构造函数(并且已经初始化了最终变量)。 但是在尝试运行它时应该得到RuntimeException

答案 3 :(得分:-1)

我假设你引用了Eclipse中的行为。 (正如评论javac的编译一样)。

我认为这是一个Eclipse问题。它有自己的编译器和自己的规则集。其中之一是您可能无法访问未初始化的字段,尽管Java-commpiler会为您初始化变量。