“this” keyword: Working mechanism in Java

若如初见. 提交于 2019-11-30 06:49:16

Other answers and comments have explained how fields are not polymorphic and how field access expressions are resolved based on the compile time type of the instance reference. Below, I explain how the byte code handles the this reference.

In the chapter about Receiving Arguments, the Java Virtual Machine Specification states

If n arguments are passed to an instance method, they are received, by convention, in the local variables numbered 1 through n of the frame created for the new method invocation. The arguments are received in the order they were passed. For example:

int addTwo(int i, int j) {
    return i + j;
}

compiles to:

Method int addTwo(int,int)
0   iload_1        // Push value of local variable 1 (i)
1   iload_2        // Push value of local variable 2 (j)
2   iadd           // Add; leave int result on operand stack
3   ireturn        // Return int result

By convention, an instance method is passed a reference to its instance in local variable 0. In the Java programming language the instance is accessible via the this keyword.

Class (static) methods do not have an instance, so for them this use of local variable 0 is unnecessary. A class method starts using local variables at index 0. If the addTwo method were a class method, its arguments would be passed in a similar way to the first version:

static int addTwoStatic(int i, int j) {
    return i + j;
}

compiles to:

Method int addTwoStatic(int,int)
0   iload_0
1   iload_1
2   iadd
3   ireturn

The only difference is that the method arguments appear starting in local variable 0 rather than 1.

In other words, you can either view this as not being declared anywhere or as being declared as the first parameter of every instance method. A local variable table entry is created for each instance method and populated on each invocation.

The chapter on Invoking methods states

The normal method invocation for a instance method dispatches on the run-time type of the object. (They are virtual, in C++ terms.) Such an invocation is implemented using the invokevirtual instruction, which takes as its argument an index to a run-time constant pool entry giving the internal form of the binary name of the class type of the object, the name of the method to invoke, and that method's descriptor (§4.3.3). To invoke the addTwo method, defined earlier as an instance method, we might write:

int add12and13() {
    return addTwo(12, 13);
}

This compiles to:

Method int add12and13()
0   aload_0             // Push local variable 0 (this)
1   bipush 12           // Push int constant 12
3   bipush 13           // Push int constant 13
5   invokevirtual #4    // Method Example.addtwo(II)I
8   ireturn             // Return int on top of operand stack;
                        // it is the int result of addTwo()

The invocation is set up by first pushing a reference to the current instance, this, on to the operand stack. The method invocation's arguments, int values 12 and 13, are then pushed. When the frame for the addTwo method is created, the arguments passed to the method become the initial values of the new frame's local variables. That is, the reference for this and the two arguments, pushed onto the operand stack by the invoker, will become the initial values of local variables 0, 1, and 2 of the invoked method.

Why x and this.x point to the x of base class and not the Child class?

Because fields in Java are not polymorphic. Fields binding is resolved at compilation time. If you wanted to use incrementing as polymorphism you could do it with a method. To perform correctly you would need to define it in parent and child.

public void increment(){
    x++; //this.x++; would do the same;
}

And if this.x points to the x of the base class, why this.b() calls the b() of child class?

Because methods on the other hand are polymorphic, which means their binding is resolved at run-time and that's why this.b() calls method from the child class, in your case this is instance of BasicInheritanceTest3 and corresponding method is called.

Is the behavior of this different for fields and methods?

As you see it is.

Super is a reference to base class, so you can access it when for example needing to call overridden methods or/and hidden fields.

EDIT Reply: this is a reference which means it is only address of the object along with all it's data in memory of JVM, how JVM handles this keyword is not really known or important, it is probably declared at instantiation. But all you need to know in the end is that this is reference to instance of Object himself.

1. Why x and this.x point to the x of base class and not the Child class?

we can see this example:

class TestBase {
    private int x;
    public void a() {
        this.x++;
    }
    public int getX() {
        return x;
    }
}
public class Test extends TestBase{
    private int x;
    public int getX() {
        return this.x;
    }
}

and generated bytecode:

public class Test extends TestBase{
public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method TestBase."<init>":()V
   4:   return

public int getX();
  Code:
   0:   aload_0
   1:   getfield        #2; //Field x:I
   4:   ireturn

public void a();
  Code:
   0:   aload_0
   1:   invokespecial   #3; //Method TestBase.a:()V
   4:   return

}

In there the Test extends TestBase and the method a is compiled into the Test class, it will call it's father 1: invokespecial #3; //Method TestBase.a:()V.

the Test's getX method will call 1: getfield #2; //Field x:I from it's own constant pool table, http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

the TestBase class bytecode:

class TestBase extends java.lang.Object{
TestBase();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public void a();
  Code:
   0:   aload_0
   1:   dup
   2:   getfield        #2; //Field x:I
   5:   iconst_1
   6:   iadd
   7:   putfield        #2; //Field x:I
   10:  return

public int getX();
  Code:
   0:   aload_0
   1:   getfield        #2; //Field x:I
   4:   ireturn

}

the method a() also will get x from it's own constant pool by getfield #2; //Field x:I.

so there is another thing: the Java's getter and setter is evil.

As a matter of fact , polymorphism property in JAVA programming language can only be applied for methods that have enough qualification to be polymorphic members. You are not supposed to think of Fields as members that have the mentioned property.Thus you won't get confused about such issues anymore.

[EDIT answer] I did a little bit of research and got the following info to answer your question further. We can indeed verify that this is part of the bytecode by using a reverse engineering tool to convert the bytecode back into the java source.

Why would we find the this in the bytecode?

Because since java is multi pass compiler and since bytecode can be expected to be run on any other platform and any other machine, all the information has got to be in bytecode, enough information to be able to reverse engineer the bytecode into the source code. Furhter, since the source code would have to be the same as the original source for the bytecode, everything including the exact names of the variables and fields would have to be "somehow" kept well organized with all the information in the bytecode. Whereas C++ or pascal, unlike java, which use a single pass compiler, would mostly not keep exact names of fields and since such languages output a final "executable" file which has to be ready to be run, may care less to maintain the exact names(the instruction doesn't have to go thru another "pass") . If anybody reverse engineers an executable file (C++ or Pascal), the variable names would not be human readable. So in bytecode "this" may be represented as non-human readable format but the same could be reverse engineered back into "this". The same is not true for the single-pass compiler.

Multi Pass Compiler

Class methods cannot access instance variables or instance methods directly—they must use an object reference. Also, class methods cannot use the this keyword as there is no instance for this to refer to.

Now the first question here is: Why x and this.x point to the x of base class and not the Child class?

It is because polymorphic behavior is not applicable for fields, so the results are from the base class.

why this.b() calls the b() of child class? Is the behavior of this different for fields and methods?

With this line: BasicInheritanceTest3 bit2 = new BasicInheritanceTest3(); The only object in heap (in terms of base and child class) is the object of type BasicInheritanceTest3. So regardless of this, the call will apply to the child class method. bit2 is referencing to its own hierarchy(of inheritance) in heap.

Now - how the compiler deals with it is same as how any other key/reserved words are dealt with by the jdk. this is not allowed in the context of class methods Class methods cannot access instance variables or instance methods directly—they must use an object reference. Also, class methods cannot use the this keyword as there is no instance for this to refer to. Indeed an intriguing question hence gave the OP an up vote for the question.

More helpful info I read was along the lines of: Identifiers and reserved keywords are tokens as are single characters like + and sequences of characters like !=.

I'd like to request community to keep this topic. I hadn't explored on how jdk (both compiler and the runtime) treat the keywords and reserved words.

Java Api Docs: this

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!