Why must I use the "this" keyword for forward references?
Asked Answered
F

5

61

When I use the this keyword for accessing a non-static variable in a class, Java doesn't give any error. But when I don't use it, Java gives an error. Why must I use this?

I know when should normally I use this, but this example is very different from normal usages.

Example:

class Foo {
//  int a = b; // gives error. why ?
    int a = this.b; // no error. why ?
    int b;
    int c = b;

    int var1 = this.var2; // very interesting
    int var2 = this.var1; // very interesting
}
Fantast answered 27/10, 2017 at 4:54 Comment(5)
Possible duplicate of What is called a forward reference in Java?Inessential
Cannot reference a field before it is definedPretorius
Note that a's value will be 0 (the default for int), even if you set b. So it doesn't give an error, but it also doesn't work. Surprisingly none of the answers mention this.Onassis
@Dukeling Good point, I expanded my answer.Peluso
Related: Recursive initializer works when I add “this”?Highspeed
P
45

Variables are declared first and then assigned. That class is the same as this:

class Foo {
    int a;
    int b;
    int c = b;

    int var1;
    int var2;

    public Foo() {
        a = b;

        var1 = var2;
        var2 = var1;
    }
}

The reason you can't do int a = b; is because b is not yet defined at the time the object is created, but the object itself (i.e. this) exists with all of its member variables.

Here's a description for each:

    int a = b; // Error: b has not been defined yet
    int a = this.b; // No error: 'this' has been defined ('this' is always defined in a class)
    int b; 
    int c = b;  // No error: b has been defined on the line before  
Punctate answered 27/10, 2017 at 5:35 Comment(2)
"b is not yet defined at the time the object is created" - fields are defined at compile time and objects are created at runtime so this cannot possibly be correct. For this reason I downvoted this answer and upvoted Erwin's.Archenemy
This answer just restates the terms of the question. It does not explain why in any meaningful way.Printing
P
72

The full description is in section 8.3.3 of the Java Language Specification: "Forward References During Field Initialization"

A forward reference (referring to a variable that is not declared yet at that point) is only an error if the following are all true:

  • The declaration of an instance variable in a class or interface C appears textually after a use of the instance variable;

  • The use is a simple name in either an instance variable initializer of C or an instance initializer of C;

  • The use is not on the left hand side of an assignment;

  • C is the innermost class or interface enclosing the use.

See the bolded text: "the use is a simple name". A simple name is a variable name without further qualification. In your code, b is a simple name, but this.b is not.

But why?

The reason is, as the cursive text in the example in the JLS states:

"The restrictions above are designed to catch, at compile time, circular or otherwise malformed initializations. "

In other words, they allow this.b because they think that a qualified reference makes it more likely that you have thought carefully about what you're doing, but simply using b probably means that you made a mistake.

That's the rationale of the designers of the Java Language. Whether that is true in practice has, to my knowledge, never been researched.

Initialization order

To expand on the above, in reference to Dukeling's comment on the question, using a qualified reference this.b will likely not give you the results you want.

I'm restricting this discussion to instance variables because the OP only referred to them. The order in which the instance variables are assigned is described in JLS 12.5 Creation of New Class Instances. You need to take into account that superclass constructors are invoked first, and that initializations code (assignments and initialization blocks) are executed in textual order.

So given

int a = this.b;
int b = 2;

you will end up with a being zero (the value of b at the time that a's initializer was executed) and b being 2.

Even weirder results can be achieved if the superclass constructor invokes a method that is overridden in the subclass and that method assigns a value to b.

So, in general, it is a good idea to believe the compiler and either reorder your fields or to fix the underlying problem in case of circular initializations.

If you need to use this.b to get around the compiler error, then you're probably writing code that will be very hard to maintain by the person after you.

Peluso answered 27/10, 2017 at 5:43 Comment(2)
Nice to see they removed the nonsensical line from the SE 7 version: It is a compile-time error if any of the four requirements above are not met.Huesman
@Huesman It has been reformulated again in the JDK9 JLS. Where rather than "before" they call it a reference "to the left" (of the declarator of the field). It still seems to mean the same. docs.oracle.com/javase/specs/jls/se9/html/jls-8.html#jls-8.3.3Peluso
P
45

Variables are declared first and then assigned. That class is the same as this:

class Foo {
    int a;
    int b;
    int c = b;

    int var1;
    int var2;

    public Foo() {
        a = b;

        var1 = var2;
        var2 = var1;
    }
}

The reason you can't do int a = b; is because b is not yet defined at the time the object is created, but the object itself (i.e. this) exists with all of its member variables.

Here's a description for each:

    int a = b; // Error: b has not been defined yet
    int a = this.b; // No error: 'this' has been defined ('this' is always defined in a class)
    int b; 
    int c = b;  // No error: b has been defined on the line before  
Punctate answered 27/10, 2017 at 5:35 Comment(2)
"b is not yet defined at the time the object is created" - fields are defined at compile time and objects are created at runtime so this cannot possibly be correct. For this reason I downvoted this answer and upvoted Erwin's.Archenemy
This answer just restates the terms of the question. It does not explain why in any meaningful way.Printing
P
4

You have presented 3 cases:

  1. int a = b; int b;
    This gives error because the compiler will look for b in the memory and it will not be there. but when you use this keyword then it specifies explicitly that the b is defined in the scope of the class, all class references will be looked up for it, and finally it will find it.
  2. Second scenario is pretty simple and as I described, b is defined in the scope before c and will not be a problem while looking for b in the memory.
  3. int var1 = this.var2;
    int var2 = this.var1;
    In this case no error because in each case the variable is defined in the class and assignment uses this which will look for the assigned variable in the class, not just the context it is followed by.
Peck answered 27/10, 2017 at 6:16 Comment(0)
C
4

For any class in Java this is a default reference variable (when no specific reference is given) that either user can give or the compiler will provide inside a non-static block. For example

public class ThisKeywordForwardReference {

    public ThisKeywordForwardReference() {
        super();
        System.out.println(b);
    }

    int a;
    int b;

    public ThisKeywordForwardReference(int a, int b) {
        super();
        this.a = a;
        this.b = b;
    }

}

You said that int a = b; // gives error. why ? gives compile time error because b is declared after a which is an Illegal Forward Reference in Java and considered as a compile-time error.

But in the case of methods Forward Reference becomes legal

int a = test();
int b;

int test() {
    return 0;
}

But in my code, the constructor with the argument is declared before both a & b, but not giving any compile-time error, because System.out.println(b); will be replaced by System.out.println(this.b); by the compiler.

The keyword this simply means current class reference or the reference on which the method, constructor or the attribute is accessed.

A a1 = new A();  // Here this is nothing but a1
a1.test();  // Here this is again a1

When we say a = this.b; it is specifying that b is a current class attribute, but when we say a = b; since it is not inside a non-static block this won't be present and will look for an attribute declared previously which is not present.

Colangelo answered 27/10, 2017 at 6:22 Comment(1)
A forward reference inside a method or constructor is always fine. Forward references to an instance variable can only be a problem "in either an instance variable initializer of C or an instance initializer of C" (JLS)Peluso
B
3

Please look at the Java Language Specification: https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.2.3

This is the reason, IMO: The usage is via a simple name.

So in this case you have to specify the name using this.

Bandur answered 27/10, 2017 at 5:38 Comment(1)
Note this line in the final example: int l = this.j * 3; // ok - not accessed via simple nameHuesman

© 2022 - 2024 — McMap. All rights reserved.