After learning Java for sometime, its the first time the use of this
keyword has confused me so much.
Here is how I got confused. I wrote the following
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 theaddTwo
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 theaddTwo
method is created, the arguments passed to the method become the initial values of the new frame's local variables. That is, the reference forthis
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.