How is the method invocation related to its definition?
As we stated in the introduction, defining a method doesn’t actually cause it to be executed; instead, when we define a method, we are “teaching” the computer—more correctly, adding this method to the capabilities of the class in which the method is defined, or to the instances of that class. It is only when the method is invoked that it is executed.
However, before a method can be invoked at runtime, the definition of the method must be compiled, along with its invocations. When a method invocation is compiled, the Java compiler compares the invocation to the compiled method declaration, to make sure that the invocation will be valid at runtime. In order to write Java code effectively, we need to understand how this comparison is performed.
The syntax of the method definition was already introduced in “Definition”. Now, let’s take a look at the invocation syntax:
[contextQualifier.]methodName([argument[, …]])
(Note that this does not show assignment of a method’s return value to a variable, or use of that return value in some other expression. At runtime, that assignment or other use takes place after the invoked method has completed processing; it is not actually part of the invocation syntax. However, when the value returned from a method invocation is used in a surrounding expression, that use will also be checked during compilation, for compatibility between the return type of the method and the type expected in the surrounding expression.)
contextQualifierAn instance (non-static) method must be invoked in the context of an object reference, whose reference type is the class (or interface) in which the method is declared, or a subclass (or subinterface) of that.
In an earlier example, we had this invocation of the convertC2F instance method, in the second line of a code fragment that also included creation of an instance of the Temperature class, followed by a declaration-with-assignment statement:
Temperature t = new Temperature();
double f = t.convertC2F(100);
Here, the context qualifier is the instance of Temperature referenced by the variable t.
A static method is invoked in the context of the class or interface (or a subclass or subinterface of that) in which the method is defined; thus, an appropriate value for this placeholder is a class name.
In the static version of the convertC2F example, we had this invocation of a static method, as part of a declaration-with-assignment statement:
double f = Temperature.convertC2F(100);
Here, the context qualifier is the Temperature class itself.
If method A is being invoked from another method, B, and the context in which B was invoked is the same one in which A should be invoked, the context qualifier may be omitted. In practical terms, this means that when invoking a static method from a static or instance method in the same class, or when invoking an instance method from an instance method in the same class, we can usually leave the context qualifier out.
methodNameThe name of the method to be invoked; this must match the declared method name exactly.
argumentAn expression which will be evaluated prior to invocation, with the resulting value passed to the method being invoked. Any arguments must match the declared parameters of the method being invoked—that is, each argument’s type must be compatible with the type of the corresponding parameter, without skipping or reordering any of the arguments or parameters. (Java does not support optional parameters: every parameter in a method declaration must be matched by a corresponding argument in the invocation, or the invocation will not compile.)
If the method being invoked has no declared parameters, the invocation must similarly include no arguments—but the parentheses are still required.
When a method definition—or just the declaration, for an abstract method—is compiled, the resulting bytecode contains the method signature (the combination of method name and parameter types, order, and number), along with the other elements of the method declaration (modifiers, return type, and throws clause, if any). Of course, the method implementation (if any) is compiled into the bytecode—but it is the declaration, not the implementation, that is used in when compiling any invocations of the method.
As noted above, an invocation of a method must match the declaration of an accessible method in the class specified by the invocation’s context qualifier. For example, if the invocation context is a class name, then the invocation must match a static method defined in that class, or defined in a superclass or implemented interface (or superinterface) of it. On the other hand, if the invocation context is a reference to an instance of a given class, then the invocation must match an instance or static method declared in that class (or in a superclass or implemented interface). If a match is not found, compilation of the invocation fails.
Here, we have a statement that successfully compiles to invoke the static method atan2, defined in the Math class.
double theta = Math.atan2(-1.0, 1.0);
The invocation context (the Math class) matches the class where the method is defined.
Each of the arguments’ types (both are double) is compatible with the type of the corresponding parameter (also double) in the method declaration.
The type of theta (the assignment target) is double, which is compatible with the declared return type (again, double) of the atan2 method.
On the other hand, we have an invocation of the charAt method, defined in the String class, that doesn’t compile:.
char c = "bootcamp".charAt(5.0);
There is a method named charAt defined in the String class. (So far, so good.)
charAt is an instance (non-static) method, and the literal value "bootcamp" is an instance of the String class, so the invocation context is correct.
The return type of charAt is char, which matches the type of the assignment target, c.
However, the charAt method is declared with a parameter of type int, and the argument 5.0 is a literal value of type double—which is not assignment-compatible with the int type of the parameter.
The problem shown above can be resolved by using a literal int value (or any expression that’s assignment-compatible to an int) as an argument to the charAt method. For example, the following invocation will compile (and execute) successfully:
char c = "bootcamp".charAt(5);