This article aims at giving a more understandable description of what a method invocation expression really is. The Java Language Specification is targeted at people having a certain mathematical background. What we are trying to do here is to explain its content to people having a moderate experience in technical English and willing to get to know Java in a more advanced way without coping with pure maths.
There are a lot of things going on under the hood when methods are invoked. First of all, a method is to be seen as a service a class provides to itself and/or to other classes.
Whenever the compiler encounters a method invocation it has to figure out which method is to be invoked and most importantly where to find its implementation.
To get the ball rolling let's just think of some real-life examples. If we want to order a pizza, we have to call a pizza delivery place and not the immigration office or the hospital. If we want to buy a car, we sure don't go to Starbucks or the train station. Put shortly, we have to find the right place where we are sure we are going to get the service we need.
"we" stands here for the compiler which is in charge of turning the human-readable Java code you have written into bytecode the Java Virtual Machine (JVM) will understand. And that's exactly how we are going to proceed here, we will try to "think" exactly the same way the compiler does. Trying to figure out what the compiler does and how it works is an excellent exercise not only to gain in-depth knowledge of the compilation process but also to spare development time by recognizing code that will result in compile-time and run-time errors.
The following table shows how this article is organized. In order to keep things pretty much organized the same way as in Section 15.12 of the the Java Language Specification (JLS), we decided to keep the same structure. The first part discusses the three steps performed at compile-time while the second focuses on what is achieved at run-time by the linker and interpreter.
|Compile Time||Step 1||Determine Class or Interface to Search|
|Step 2||Determine Method Signature|
|Step 3||Is The Chosen Method Appropriate?|
|Run-time||Step 4||Compute Target Reference (If Necessary)|
|Step 5||Evaluate Arguments|
|Step 6||Check Accessibility of Type and Method|
|Step 7||Locate Method to Invoke|
|Step 8||Create Frame, Synchronize, Transfer Control|
Ok, let's go. Below is a slightly modified version of the method invocation expression given in JLS 15.12:
MethodInvocation: Identifier (ArgumentListopt) TypeName . Identifier (ArgumentListopt) FieldName . Identifier (ArgumentListopt) Primary . Identifier (ArgumentListopt) super . Identifier (ArgumentListopt) ClassName . super . Identifier (ArgumentListopt) ArgumentList: Expression ArgumentList , Expression
Although those expressions may seem overly complex, they are not. We can see that the part after the left paranthesis, that is ArgumentListopt), is common to all four cases. By investigating the form BEFORE the left paranthesis, we notice that we always find an Identifier which may be qualified or not. To qualify a method means that we have a clue of where the method may reside and we want to specify it (with the dot-notation). So, we will look into those six different cases and explain when they are used and what purposes they serve.
Moreover, we are going to illustrate the concepts using the following example which contains at least one sample of each expression given above:
The compiler first has to discover the class --what we called a "place"-- in which the method --what we called a "service"-- we need is defined.
MethodInvocationwhereas line 7 invokes
getClasswhich is inherited from the superclass
Objectand line 22 invokes the static method
getMethodInvocationInstancedefined in the same class, that is
aStaticMethoddefined in the class
MethodInvocation. Note that since the method
aStaticMethodis in the same class as the invocation expression, it has the same functionality as the unqualified invocation on lines 6 and 20.
exitdefined in the class
System. In this case, the qualified invocation expression is needed since we have to specify where the method called
exitis to be found, that is in class
anotherInstanceMethodon the reference
|Note: Readability sure is a matter of personal taste. But the bottom line is that it is much better to
settle for some kind of consistence while coding, that is invoke instance methods declared in the same class using
the keyword |
getMethodInvocationInstance, is what we call the Primary expression here (you may notice that the method is invoked using the very first form we have discussed above). Then, the return type of that method (
MethodInvocation) is used to determine the class or interface which provides the implementation of the method on the right side,
anInstanceMethod. Put shortly,
getMethodInvocationInstanceis invoked and returns an instance of the class
MethodInvocationupon which the method
anInstanceMethodwill then be invoked.
Objecthas no superclass) or when the invocation is contained within an interface (interfaces do not contain any implementation).
MethodInvocationcontains an overridden declaration of the
toStringmethod originally declared in class
Object. But for now we are happy with the default implementation of method
toStringand decide to rely on the behavior provided by class
Objectuntil our needs evolve. Thus, we invoke
Objectusing the prefix super and return the result.
Objector an interface (for the same reason stated in the fifth form).
The goal of this first step was to determine the class or interface which contains the method to invoke. Now that we know exactly where to search we have to look for the right method to invoke. This is done in the second step where we will investigate which method signature fits best the invocation expression.
In the rest of this article, we will use the same code examples as in the JLS.
The choice of the right method is based on the method descriptor.
Method signature? Method descriptor? What are they? The method signature is composed of the method name
and the parameter list (ordered!). The method descriptor takes the method signature AND the return type.|
Two conditions must be met for a method declaration to be applicable:
Stringis required, for instance.
The class or interface we found in the previous step as well as all its superclasses and superinterfaces are searched for all method declarations applicable to the method invocation we are trying to resolve.
Accessibility depends on what type of access the method has been granted (
private) and where the invocation expression appears.
6.6 (Access Control) of the JLS for specific details concerning accessibility.
To get a better understanding of the applicability and accessibility concepts, let's have a look at the
following code borrowed from
188.8.131.52 (Find Methods that are Applicable and Accessible):
intargument (1) from within class
Doubler. We can see that we have two methods named
Doublerbut only the second one (on line 3) is applicable because its parameter list matches the argument list of the invocation expression on line 2. At runtime, the method named
twoon line 3 will be invoked.
intargument (3) from within class
Test. From the latter's perspective, we have three methods named
twobut the one on line 2 is not applicable because the parameter list is empty. The one on line 3 is applicable since it fulfills the applicability requirements stated above but it is not accessible (
privateaccessibility) and thus cannot be used. The only applicable and accessible method is the one on line 6 (Note that the argument 3 is converted to
long) within class
privatemodifier!!) method in class
Doubler. The compiler will choke on this and spit some error stating that:
two(int) has private access in Doubler, which is quite understandable.
If we don't manage to find any method declaration that is both applicable and accessible, then an error during the compilation process will result as it is the case on line 9.
The case where several methods are applicable and accessible arises while
overloading methods. What do we do then? We just have to choose the most specific
one. How do we do that?
The JLS says:
The informal intuition is that one method declaration is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error.
public void doSomeJob(String s)
public void doSomeJob(Object o)
Stringbut also an
Object). Always come to think of the most specific method as the one having its parameter types matching the best the argument types of the invocation. So, the first method is more specific than the second one because its parameter (
String) can be converted to the second's parameter (
Object) by method invocation conversion (see JLS 5.3).
As we have seen above, we may end up with several methods being accessible and applicable.
The final goal of this step is to find the maximally specific method. Put simply,
a method is maximally specific if it is applicable and accessible and there is no other applicable
and accessible method that is more specific.
Now, as the flowchart below will show, it is possible to reach different verdicts:
public void doSomeJob(String s, Object o)
public void doSomeJob(Object o, String s)
public void doSomeJob(String s1, String s2)
Another important consideration is that the return type of the method is not taken into
account when resolving method declarations. The following example illustrates the problem:
ColoredPointargument) but the problem is that the return type,
int, is not assignment compatible with a
String. The method on line 7 is also applicable but less specific than the method on line 4. Moreover, by choosing the method on line 7 the compilation would succeed because of the return type,
However, you could make this code compilable by changing line 12 to:|
This way the returned
If we managed to get to this step, it means that we have found THE most specific method matching the invocation expression and we call it the compile-time declaration for the method invocation. Now what? Remember the invocation patterns we saw at step 1? We now have to check if the chosen method can be used in the given context, that is, at the place where the invocation occurs. As an example, you certainly already know that you cannot invoke an instance method from within the body of a static method. We have to make sure that such things don't happen. This is what this step is for.
If the invocation pattern is:
thisis not available in static contexts).
staticotherwise a compile-time error occurs. The reason is because an instance method must be invoked on an object and not on a class. (while a static method can be invoked both on a class or on an instance as we will see later.)
thisto refer to the current instance nor a
superto refer to the superclass instance.)
voidthen the method cannot be used where a value is expected. For instance, suppose we have the following expression:
String s = doSomething();
doSomething's return type is
voidthen a compile-time error occurs.
voidcan only be used as an expression statement, that is, alone on a line, or in the initialization or update part of a
forstatement (see JLS 14.13).
Finally, if the chosen method passes all tests successfully, the compilation will succeeds
and we can proceed to runtime checks. The bytecode of the method invocation now contains
the following information that can be used at runtime by the Java bytecode interpreter:
voidIS NOT a primitive type in Java, see this discussion at JavaRanch);
static: if the method declaration contains the
non-virtual: if the method is declared
super: if the invocation pattern is super . Identifier or ClassName . super . Identifier;
interface: if the method is declared in a interface;
virtualfor any other cases.
A target reference is a reference to an instance of a class (an object) on which the method
will be invoked. No target references are needed for
static methods, that is,
methods whose invocation mode is
The target reference is computed differently depending on the invocation pattern.
If the invocation expression is:
static, there is no need for a target reference. That means that if a
staticmethod is invoked upon a
NullPointerExceptionwill be thrown.
static, there is no target reference.
static, there is no target reference. The expression Primary is evaluated but the result is discarded anyway.
ClassName . this
This step's job is to evaluate the argument expression from left to right. If any argument expression evaluation fails, then no argument expression on its right will be evaluated and the method invocation expression fails for the same reason.
We now have to determine if the type of the target reference and the method to invoke are accessible. Three different entities are involved here:
Ccontaining the invocation;
Any Java programming language implementation must check during linkage that:
mexists in class/interface
T, otherwise a
interface, then the target reference type must implement the specified interface otherwise an
Accessibility is the next issue. Since the invocation of method
T) occurs in class
C, the compiler has to make
Tis accessible from
C, and then that
T) is also accessible from
T is accessible from class
T) is accessible from class
if either one of the following is true:
protected, and either one of the following is true:
Tare in the same package.
protectedmethods are available throughout a package.)
Tare the same class.
protectedmethods are available throughout a class.)
Cis a subclass of
protectedmethods are available to the defining class and to all subclasses as well.)
mhas default access (no modifier) and
Tare in the same package.
mis private and either one of the following is true:
Tare the same class.
privatemethods are available throughout a class.)
C, that is, one is an inner class of the other.
privatemethods are available to inner class.)
Tare both enclosed by a third class.
privatemethods are available to inner class.)
If this paragraph seems overly complex to you, rest assured that it wasn't out
original intent. The content is very logical. Just read it once or twice before going on.
Think of these accessibility issues as a sort of labyrinth where not only one but several
paths lead to the exit and some don't. Everything depends on the accessibility modifiers.
The next picture will help you find your way through the labyrinth.
If either of the accessibility checks fails, then an
We now have to locate the method to invoke. The invocation mode (step 3)
provides us with that information. After this step we end up with a precise method to invoke.
Don't worry if you feel lost after reading this paragraph, the meaning is not very easy to grasp
and, moreover, this is the most important but also the most difficult step of the whole method
invocation process. Read this section twice or thrice if you feel the need to do so.
Remember that we have 5 different invocation modes:
|We don't need any target reference (on which to invoke the method), overriding is NOT allowed and method
Every subsequent invocation mode handles an instance method invocation, we therefore need the target reference
on which the method will be invoked. If this reference is |
|Overriding is NOT allowed and method |
For the three remaining invocation modes, overriding may occur. That's why we need a dynamic method lookup process.
The process starts looking for the method in class
S (and then if needed in the superclasses of
S is the runtime class of the object on which the method is invoked. Moreover,
Snecessarly implements interface
Tor a subclass of
Sis the class/interface in which the method is declared.
The method lookup for
super invocation mode
is as follows:
S contains a non-
abstract declaration of a method named
the same descriptor, that is, same number of parameters, same parameter type AND same return type, then:
mis the method to be invoked. The method lookup ends here.
virtualand the method declaration in
Soverrides the one in the compile-time type of the target reference of the method invocation, then
mis the method to be invoked. The method lookup ends here.
Shas a superclass, then this lookup is performed recursively on the direct superclass of
S. The result of this recursion will be the method to be invoked.
This lookup always succeeds provided that the compilation of the classes has been done in a consistent way or various errors may occur.
This step just handles
administration stuff, like creating a new activation frame, synchronizing data access if
needed and transferring control to the method body.
We won't go into very much details here. If you want to know exactly what an activation frame is, or what synchonization means, please refer to the given hyperlinks.
At the end of the previous step, we ended up with the actual method to invoke. The only
thing that remains to be done is to execute it. To do so, we create a new activation frame containing:
This new activation frame becomes the new current activation frame and the argument values are assigned to the newly created parameter variables (available within the method's body). Just think of the activation frame as a context for the method execution that contains everything that the method may need to execute properly.
If the method is declared |
Finally, if the method is not
If the method is:
Written by Valentin Crettaz.