为什么要编译这些 Java 代码?

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

int x = x = 1;

在类范围 变量在哪里得到它们的默认值中,下面给出了“未定义引用”错误:

int x = x + 1;

这不是第一个 x = x = 1应该以同样的“未定义引用”错误结束吗?或者也许应该编译第二行 int x = x + 1?还是我遗漏了什么?

8426 次浏览

Your first piece of code contains a second = instead of a plus. This will compile anywhere while the second piece of code will not compile in either place.

In int x = x + 1; you add 1 to x , so what is the value of x, it's not created yet.

But in int x=x=1; will compile with no error because you assign 1 to x.

In the second piece of code, x is used before its declaration, while in the the first piece of code it is just assigned twice which doesn't make sense but is valid.

It's roughly equivalent to:

int x;
x = 1;
x = 1;

Firstly, int <var> = <expression>; is always equivalent to

int <var>;
<var> = <expression>;

In this case, your expression is x = 1, which is also a statement. x = 1 is a valid statement, since the var x has already been declared. It is also an expression with the value 1, which is then assigned to x again.

The second one int x=x=1 is compile because you are assigning the value to the x but in other case int x=x+1 here the variable x is not initialized , Remember in java local variable are not initialized to default value. Note If it's (int x=x+1) in class scope also then also it will give compilation error as the variable is not created.

int x = x + 1;

compiles successfully in Visual Studio 2008 with warning

warning C4700: uninitialized local variable 'x' used`
int x = x = 1;

is equivalent to

int x = 1;
x = x; //warning here

while in

int x = x + 1;

first we need to compute x+1 but the value of x is not known so you get an error (the compiler knows that the value of x is not known)

x is not initialised in x = x + 1;.

The Java programming language is statically-typed, which means that all variables must first be declared before they can be used.

See primitive data types

In java or in any modern language, assignment comes from the right.

Suppose if you are having two variables x and y,

int z = x = y = 5;

This statement is valid and this is how the compiler splits them.

y = 5;
x = y;
z = x; // which will be 5

But in your case

int x = x + 1;

The compiler gave an exception because, it splits like this.

x = 1; // oops, it isn't declared because assignment comes from the right.

Let's break it down step by step, right associative

int x = x = 1

x = 1, assign 1 to a variable x

int x = x, assign what x is to itself, as an int. Since x was previously assigned as 1, it retains 1, albeit in a redundant fashion.

That compiles fine.

int x = x + 1

x + 1, add one to a variable x. However, x being undefined this will cause a compile error.

int x = x + 1, thus this line compile errors as the right portion of the equals will not compile adding one to an unassigned variable

int x = x = 1; is not equal to:

int x;
x = 1;
x = x;

javap helps us again, these are JVM instruction generated for this code:

0: iconst_1    //load constant to stack
1: dup         //duplicate it
2: istore_1    //set x to constant
3: istore_1    //set x to constant

more like:

int x = 1;
x = 1;

Here is no reason to throw undefined reference error. There is now usage of variable prior to it's initialization, so this code fully comply with specification. In fact there is no usage of variable at all, just assignments. And JIT compiler will go even further, it will eliminate such constructions. Saying honestly, I don't understand how this code is connected to JLS's specification of variable initialization and usage. No usage no problems. ;)

Please correct if I'am wrong. I cant figure out why other answers, which refer to many JLS paragraphs collect so many pluses. These paragraphs has nothing in common with this case. Just two serial assignments and no more.

If we write:

int b, c, d, e, f;
int a = b = c = d = e = f = 5;

is equal to:

f = 5
e = 5
d = 5
c = 5
b = 5
a = 5

Right most expression is just assigned to variables one by one, without any recursion. We can mess variables any way we like :

a = b = c = f = e = d = a = a = a = a = a = e = f = 5;

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.

The line of code does not compile with a warning because of how the code actually works. When you run the code int x = x = 1, Java first creates the variable x, as defined. Then it runs the assignment code (x = 1). Since x is already defined, the system has no errors setting x to 1. This returns the value 1, because that is now the value of x. Therefor, x is now finally set as 1.
Java basically executes the code as if it was this:

int x;
x = (x = 1); // (x = 1) returns 1 so there is no error

However, in your second piece of code, int x = x + 1, the + 1 statement requires x to be defined, which by then it is not. Since assignment statements always mean the code to the right of the = is run first, the code will fail because x is undefined. Java would run the code like this:

int x;
x = x + 1; // this line causes the error because `x` is undefined

Complier read statements from right to left and we designed to do the opposite. That's why it annoyed at first. Make this a habbit to read statements (code) from right to left you won't have such problem.