At run time, method invocation requires five steps. First, a target reference may be computed. Second, the argument expressions are evaluated. Third, the accessibility of the method to be invoked is checked. Fourth, the actual code for the method to be executed is located. Fifth, a new activation frame is created, synchronization is performed if necessary, and control is transferred to the method code.
There are several cases to consider, depending on which of the three productions for MethodInvocation (§15.11) is involved:
static
, then there is no target reference.
this
.
.
Identifier, then there is no target reference.
.
Identifier, then there are two subcases:
In either case, if the evaluation of the Primary expression completes abruptly, then no part of any argument expression appears to have been evaluated, and the method invocation completes abruptly for the same reason.
super
, is involved, then the target reference is the value of this
.
The argument expressions are evaluated in order, from left to right. If the evaluation of any argument expression completes abruptly, then no part of any argument expression to its right appears to have been evaluated, and the method invocation completes abruptly for the same reason.
Let C be the class containing the method invocation, and let T be the class or interface that contained the method being invoked, and m be the name of the method, as determined at compile time (§15.11.3).
A Java Virtual Machine must insure, as part of linkage, that the method m still exists in the type T. If this is not true, then a NoSuchMethodError
(which is a subclass of IncompatibleClassChangeError
) occurs. If the invocation mode is interface
, then the virtual machine must also check that the target reference type still implements the specified interface. If the target reference type does not still implement the interface, then an IncompatibleClassChangeError
occurs.
The virtual machine must also insure, during linkage, that the type T and the method m are accessible. For the type T:
public
, then T is accessible.
public
, then m is accessible. (All members of interfaces are public
(§9.2)).
protected
, then m is accessible if and only if either T is in the same package as C, or C is T or a subclass of T.
private
, then m is accessible if and only if and C is T.
If either T or m is not accessible, then an IllegalAccessError
occurs (§12.3).
The strategy for method lookup depends on the invocation mode.
If the invocation mode is static
, no target reference is needed and overriding is not allowed. Method m of class T is the one to be invoked.
Otherwise, an instance method is to be invoked and there is a target reference. If the target reference is null
, a NullPointerException
is thrown at this point. Otherwise, the target reference is said to refer to a target object and will be used as the value of the keyword this
in the invoked method. The other four possibilities for the invocation mode are then considered.
If the invocation mode is nonvirtual
, overriding is not allowed. Method m of class T is the one to be invoked.
Otherwise, the invocation mode is interface
, virtual
, or super
, and overriding may occur. A dynamic method lookup is used. The dynamic lookup process starts from a class S, determined as follows:
interface
or virtual
, then S is initially the actual run-time class R of the target object. If the target object is an array, R is the class Object
. (Note that for invocation mode interface
, R necessarily implements T; for invocation mode virtual
, R is necessarily either T or a subclass of T.)
super
, then S is initially the superclass of the class C that contains the method invocation.
The dynamic method lookup uses the following procedure to search class S, and then the superclasses of class S, as necessary, for method m.
IncompatibleClassChangeError
occurs if this is not the case.)
This procedure will find a suitable method when it reaches class T
, because otherwise an IllegalAccessError
would have been thrown by the checks of the previous section §15.11.4.3.
We note that the dynamic lookup process, while described here explicitly, will often be implemented implicitly, for example as a side-effect of the construction and use of per-class method dispatch tables, or the construction of other per-class structures used for efficient dispatch.
A method m in some class S has been identified as the one to be invoked.
Now a new activation frame is created, containing the target reference (if any) and the argument values (if any), as well as enough space for the local variables and stack for the method to be invoked and any other bookkeeping information that may be required by the implementation (stack pointer, program counter, reference to previous activation frame, and the like). If there is not sufficient memory available to create such an activation frame, an OutOfMemoryError
is thrown.
The newly created activation frame becomes the current activation frame. The effect of this is to assign the argument values to corresponding freshly created parameter variables of the method, and to make the target reference available as this
, if there is a target reference.
If the method m is a native
method but the necessary native, implementation-dependent binary code has not been loaded (§20.16.13, §20.16.14) or otherwise cannot be dynamically linked, then an UnsatisfiedLinkError
is thrown.
If the method m is not synchronized
, control is transferred to the body of the method m to be invoked.
If the method m is synchronized
, then an object must be locked before the transfer of control. No further progress can be made until the current thread can obtain the lock. If there is a target reference, then the target must be locked; otherwise the Class
object for class S, the class of the method m, must be locked. Control is then transferred to the body of the method m to be invoked. The object is automatically unlocked when execution of the body of the method has completed, whether normally or abruptly. The locking and unlocking behavior is exactly as if the body of the method were embedded in a synchronized
statement (§14.17).
In order to allow certain kinds of code optimization, implementations are permitted some freedom to combine activation frames. Suppose that a method invocation within class C is to invoke a method m within class S. Then the current activation frame may be used to provide space for S instead of creating a new activation frame only if one of the following conditions is true:
SecurityManager
or a subclass of SecurityManager
.
SecurityManager
or a subclass of SecurityManager
; and method m is known not to call, directly or indirectly, any method of SecurityManager
(§20.17) or any of its subclasses.
When a target reference is computed and then discarded because the invocation
mode is static
, the reference is not examined to see whether it is null
:
class Test { static void mountain() {
System.out.println("Monadnock");
} static Test favorite(){ System.out.print("Mount "); return null; } public static void main(String[] args) { favorite().mountain(); } }
Mount Monadnock
Here favorite
returns null
, yet no NullPointerException
is thrown.
As part of an instance method invocation (§15.11), there is an expression that denotes the object to be invoked. This expression appears to be fully evaluated before any part of any argument expression to the method invocation is evaluated.
class Test { public static void main(String[] args) { String s = "one"; if (s.startsWith(s = "two")) System.out.println("oops"); } }
the occurrence of s
before ".startsWith
" is evaluated first, before the argument
expression s="two"
. Therefore, a reference to the string "one"
is remembered as
the target reference before the local variable s is changed to refer to the string
"two"
. As a result, the startsWith
method (§20.12.20) is invoked for target
object "one"
with argument "two"
, so the result of the invocation is false
, as the
string "one"
does not start with "two"
. It follows that the test program does not
print "oops
".
class Point { final int EDGE = 20; int x, y; void move(int dx, int dy) { x += dx; y += dy; if (Math.abs(x) >= EDGE || Math.abs(y) >= EDGE) clear(); } void clear() { System.out.println("\tPoint clear"); x = 0; y = 0; } } class ColoredPoint extends Point { int color;
void clear() { System.out.println("\tColoredPoint clear"); super.clear(); color = 0; } }
the subclass ColoredPoint
extends the clear
abstraction defined by its superclass Point
. It does so by overriding the clear
method with its own method,
which invokes the clear
method of its superclass, using the form super.clear
.
This method is then invoked whenever the target object for an invocation of clear
is a ColoredPoint
. Even the method move
in Point
invokes the clear
method of class ColoredPoint
when the class of this
is ColoredPoint
, as shown by the output of this test program:
class Test { public static void main(String[] args) { Point p = new Point(); System.out.println("p.move(20,20):"); p.move(20, 20); ColoredPoint cp = new ColoredPoint(); System.out.println("cp.move(20,20):"); cp.move(20, 20); p = new ColoredPoint(); System.out.println("p.move(20,20), p colored:"); p.move(20, 20); } }
p.move(20,20): Point clear cp.move(20,20): ColoredPoint clear Point clear p.move(20,20), p colored: ColoredPoint clear Point clear
Overriding is sometimes called "late-bound self-reference"; in this example it means that the reference to clear
in the body of Point.move
(which is really syntactic shorthand for this.clear
) invokes a method chosen "late" (at run time, based on the run-time class of the object referenced by this
) rather than a method chosen "early" (at compile time, based only on the type of this
). This provides the Java programmer a powerful way of extending abstractions and is a key idea in object-oriented programming.
An overridden instance method of a superclass may be accessed by using the keyword super
to access the members of the immediate superclass, bypassing any
overriding declaration in the class that contains the method invocation.
When accessing an instance variable, super
means the same as a cast of this
(§15.10.2), but this equivalence does not hold true for method invocation. This is demonstrated by the example:
class T1 { String s() { return "1"; } } class T2 extends T1 { String s() { return "2"; } } class T3 extends T2 { String s() { return "3"; } void test() { System.out.println("s()=\t\t"+s()); System.out.println("super.s()=\t"+super.s()); System.out.print("((T2)this).s()=\t"); System.out.println(((T2)this).s()); System.out.print("((T1)this).s()=\t"); System.out.println(((T1)this).s()); } } class Test { public static void main(String[] args) { T3 t3 = new T3(); t3.test(); } }
s()= 3 super.s()= 2 ((T2)this).s()= 3 ((T1)this).s()= 3
The casts to types T1
and T2
do not change the method that is invoked, because
the instance method to be invoked is chosen according to the run-time class of the
object referred to be this
. A cast does not change the class of an object; it only
checks that the class is compatible with the specified type.