为什么这个Java代码会编译? [英] Why does this Java code compile?

查看:125
本文介绍了为什么这个Java代码会编译?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在方法或类范围中,下面的行编译(带警告):

  int x = x = 1; 

在课程范围内,变量获取默认值,以下给出'undefined reference'错误:

  int x = x + 1; 

不是第一个 x = x = 1 应该以相同的'undefined reference'错误结束?或者第二行 int x = x + 1 应该编译?或者有一些我缺少的东西?

解决方案

tl; dr



对于字段 int b = b + 1 是非法的,因为 b 是非法的前向参考 b 。您实际上可以通过编写 int b = this.b + 1 来解决此问题,该编译无需投诉。



For 局部变量 int d = d + 1 是非法的,因为 d 之前未初始化使用。这是字段的情况,它们始终默认初始化。



您可以通过尝试编译来看到差异



int x =(x = 1)+ x;



作为字段声明和局部变量声明。前者会失败,但后者会成功,因为语义不同。



简介



首先off,字段和局部变量初始化程序的规则是非常不同的。所以这个答案将分两部分来处理规则。



我们将在整个过程中使用这个测试程序:

 公共类测试{
int a = a = 1;
int b = b + 1;
public static void Main(String [] args){
int c = c = 1;
int d = d + 1;
}
}

声明 b 无效并因非法转发引用错误而失败。

声明 d 无效并且失败且变量d可能尚未初始化错误。



事实是这些错误是不同的,应该暗示错误的原因也不同。



字段



字段初始值设定项Java受JLS§8.3的约束.2 ,字段初始化。



字段的范围JLS§6.3,声明的范围。



相关规则是:




  • 成员声明的范围 m 在类中声明或继承类型C(第8.1.6节)是C的整个主体,包括任何嵌套类型声明。

  • 实例变量的初始化表达式可以使用在或继承的任何静态变量的简单名称在课堂上,甚至是一个声明后来以文本形式出现的人。

  • 使用在使用后以声明方式显示声明的实例变量有时会受到限制,即使这些实例变量在范围内也是如此。有关控制实例变量的正向引用的精确规则,请参见§8.3.2.3。



§8.3.2.3说:


成员的声明需要以文本形式出现,只有当成员是
a类或接口C的实例(分别是静态)字段并且满足以下所有条件时才使用




  • 用法发生在C的实例(分别是静态)变量初始值设定项或C的实例(分别是静态)初始值设定项中。

  • 使用情况不在作业的左侧。

  • 使用方法是通过一个简单的名称。

  • C是封闭用法的最内层的类或接口。


你可以在它们拥有之前实际引用字段除非在某些情况下被宣布。这些限制旨在防止类似

  int j = i; 
int i = j;

来自编译。 Java规范说上述限制旨在捕获,在编译时,循环或其他格式错误的初始化。



这些规则实际归结为什么?



简而言之,规则基本上是指<​​em>必须在对该字段的引用之前声明一个字段,如果(a)引用是在初始化程序,(b)引用未分配给,(c)引用是简单名称(没有限定符,如 this。)和(d)不是从内部阶级进入的。因此,满足所有四个条件的前向引用是非法的,但是至少在一个条件上失败的前向引用是可以的。



int a = a = 1; 编译,因为它违反了(b):引用 a 被分配给,所以它是合法地在 a 的完整声明之前参考 a



int b = this.b + 1 也会编译,因为它违反了(c):引用 this.b 不是一个简单的名称(它符合这个。)。这个奇怪的构造仍然是完美定义的,因为 this.b 的值为零。



所以,基本上,初始值设定项中字段引用的限制阻止 int a = a + 1 成功编译。



观察到字段声明 int b =(b = 1)+ b 失败进行编译,因为最终 b 仍然是非法的前向参考。



本地变量



本地变量声明受以下因素控制: JLS§14.4,本地变量声明声明。



局部变量的范围JLS§6.3,声明范围:




  • 块(第14.4节)中局部变量声明的范围是声明出现的块的其余部分,从其自己的初始化程序开始,并在本地变量声明语句中包含右侧的任何其他声明符。



请注意,初始值设定项在声明的变量范围内。那么为什么 int d = d + 1; compile?



原因是由于Java的规则明确作业JLS§16 )。确定赋值基本上表示对局部变量的每次访问必须具有对该变量的先前赋值,并且Java编译器检查循环和分支以确保在任何使用之前发生赋值始终(这就是为什么确定赋值有一个专门用于它的整个规范部分)。基本规则是:




  • 每次访问本地变量或空白的最终字段 x x ,否则会发生编译时错误。



int d = d + 1; 中,访问 d 被解析为局部变量罚款,但由于 d 尚未在 d之前分配,编译器发出错误。在 int c = c = 1 中, c = 1 首先发生,它指定 c ,然后 c 初始化为该作业的结果(即1)。



<请注意,由于明确的赋值规则,局部变量声明 int d =(d = 1)+ d; 成功编译(不像字段声明 int b =(b = 1)+ b ),因为 d 绝对是在达到最终 d 时分配的。


In method or class scope, the line below compiles (with warning):

int x = x = 1;

In class scope, where variables get their default values, the following gives 'undefined reference' error:

int x = x + 1;

Isn't it the first x = x = 1 should end up with same 'undefined reference' error? Or maybe the second line int x = x + 1 should compile? Or there is something I am missing?

解决方案

tl;dr

For fields, int b = b + 1 is illegal because b is an illegal forward reference to b. You can actually fix this by writing int b = this.b + 1, which compiles without complaints.

For local variables, int d = d + 1 is illegal because d is not initialized before use. This is not the case for fields, which are always default-initialized.

You can see the difference by attempting to compile

int x = (x = 1) + x;

as a field declaration and as a local variable declaration. The former will fail, but the latter will succeed, because of the difference in semantics.

Introduction

First off, the rules for field and local variable initializers are very different. So this answer will tackle the rules in two parts.

We'll use this test program throughout:

public class test {
    int a = a = 1;
    int b = b + 1;
    public static void Main(String[] args) {
        int c = c = 1;
        int d = d + 1;
    }
}

The declaration of b is invalid and fails with an illegal forward reference error.
The declaration of d is invalid and fails with an variable d might not have been initialized error.

The fact that these errors are different should hint that the reasons for the errors are also different.

Fields

Field initializers in Java are governed by JLS §8.3.2, Initialization of Fields.

The scope of a field is defined in JLS §6.3, Scope of a Declaration.

Relevant rules are:

  • The scope of a declaration of a member m declared in or inherited by a class type C (§8.1.6) is the entire body of C, including any nested type declarations.
  • Initialization expressions for instance variables may use the simple name of any static variable declared in or inherited by the class, even one whose declaration occurs textually later.
  • Use of instance variables whose declarations appear textually after the use is sometimes restricted, even though these instance variables are in scope. See §8.3.2.3 for the precise rules governing forward reference to instance variables.

§8.3.2.3 says:

The declaration of a member needs to appear textually before it is used only if the member is an instance (respectively static) field of a class or interface C and all of the following conditions hold:

  • The usage occurs in an instance (respectively static) variable initializer of C or in an instance (respectively static) initializer of C.
  • The usage is not on the left hand side of an assignment.
  • The usage is via a simple name.
  • C is the innermost class or interface enclosing the usage.

You can actually refer to fields before they have been declared, except in certain cases. These restrictions are intended to prevent code like

int j = i;
int i = j;

from compiling. The Java spec says "the restrictions above are designed to catch, at compile time, circular or otherwise malformed initializations."

What do these rules actually boil down to?

In short, the rules basically say that you must declare a field in advance of a reference to that field if (a) the reference is in an initializer, (b) the reference is not being assigned to, (c) the reference is a simple name (no qualifiers like this.) and (d) it is not being accessed from within an inner class. So, a forward reference that satisfies all four conditions is illegal, but a forward reference that fails on at least one condition is OK.

int a = a = 1; compiles because it violates (b): the reference a is being assigned to, so it's legal to refer to a in advance of a's complete declaration.

int b = this.b + 1 also compiles because it violates (c): the reference this.b is not a simple name (it's qualified with this.). This odd construct is still perfectly well-defined, because this.b has the value zero.

So, basically, the restrictions on field references within initializers prevent int a = a + 1 from being successfully compiled.

Observe that the field declaration int b = (b = 1) + b will fail to compile, because the final b is still an illegal forward reference.

Local variables

Local variable declarations are governed by JLS §14.4, Local Variable Declaration Statements.

The scope of a local variable is defined in JLS §6.3, Scope of a Declaration:

  • The scope of a local variable declaration in a block (§14.4) is the rest of the block in which the declaration appears, starting with its own initializer and including any further declarators to the right in the local variable declaration statement.

Note that initializers are within the scope of the variable being declared. So why doesn't int d = d + 1; compile?

The reason is due to Java's rule on definite assignment (JLS §16). Definite assignment basically says that every access to a local variable must have a preceding assignment to that variable, and the Java compiler checks loops and branches to ensure that assignment always occurs prior to any use (this is why definite assignment has an entire specification section dedicated to it). The basic rule is:

  • For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.

In int d = d + 1;, the access to d is resolved to the local variable fine, but since d has not been assigned before d is accessed, the compiler issues an error. In int c = c = 1, c = 1 happens first, which assigns c, and then c is initialized to the result of that assignment (which is 1).

Note that because of definite assignment rules, the local variable declaration int d = (d = 1) + d; will compile successfully (unlike the field declaration int b = (b = 1) + b), because d is definitely assigned by the time the final d is reached.

这篇关于为什么这个Java代码会编译?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆