OVERLOAD RESOLUTION
Suppose that you're doing some Java programming, and you have code
like this:
public class OverDemo1 {
static void f(float f) {}
static void f(double d) {}
public static void main(String args[]) {
f(37);
}
}
What happens here? It's possible to convert an integer value (37)
to either a float or a double, so which of the f methods is called?
Or is this program invalid? (The equivalent C++ program gives a
compile error.) The answers to these questions relate to
"overload resolution," that is, the rules that are applied
to determine which method is called when there is a set of
identically-named methods.
The first rule says that a method is "applicable" in a given
overloading case if the number of parameters in the method
declaration matches the number of arguments in the method
invocation, and the type of each argument can be converted to the
type of the corresponding method parameter.
You might think that a rule stating that the number of parameters
and arguments must match would be obvious, but not necessarily so.
In C++, functions are allowed default parameters, so that you can
declare a function by saying:
void f(int, double = 12.34);
and then call it with:
f(37);
which is converted by the C++ compiler to:
f(37, 12.34);
The rule about conversions can be illustrated by a slight variation
on the first example:
public class OverDemo2 {
static void f(float f) {}
static void f(String s) {}
public static void main(String args[]) {
f(37);
}
}
In this example, 37 can be converted to a float through a "widening
primitive conversion." But there's no corresponding way to convert
37 to a String, so there's no issue of choosing one f method over
the other. The compiler chooses f(float).
The second rule is that a method declaration must be "accessible,"
that is, available at the point where the method is invoked. Here
is another example:
class A {
public static void f(float f, double d) {}
private static void f(double d, float f) {}
}
public class OverDemo3 {
public static void main(String args[]) {
A.f(37, 47);
}
}
At the point where A.f is invoked within main, only one of the
f methods from A is accessible; the other one is private and not
available outside of the A class. This particular example will
trigger an ambiguity error if both f methods are public. Note that
it's never an error to simply declare more than one method with the
same name but different parameter types. The error occurs at the
point of method invocation if the compiler cannot determine which
method to invoke.
The third and final rule is the trickiest. Suppose that after the
first two rules above are applied, there are still two or more
methods with the same name that could possibly be called. For
example, in the OverDemo1 program above, neither of the two rules
already described removes either of the f methods from
consideration. Both methods have the right number of parameters,
it's possible to convert 37 to a float or a double, and both
methods are accessible.
So the third rule is to choose the most "specific" method. The
rule is: if any method still under consideration has parameter
types that are assignable to another method that's also still
in play, then the other method is removed from consideration.
This process is repeated until no other method can be eliminated.
If the result is a single "most specific" method, then that method
is called. If there's more than one method left, the call is
ambiguous.
Suppose that you have the methods:
f(float)
f(double)
In this case, the parameter types for the first method are
assignable to the parameter types of the second method, that is,
it's legal to say:
double = float
through a widening primitive conversion. By contrast, saying:
float = double
is not valid without a cast. Based on this third rule, f(double) is
removed from the set of possible methods to call, and therefore
f(float) is called. You can confirm this behavior with another demo
program:
public class OverDemo4 {
static void f(float f) {System.out.println("float");}
static void f(double d) {System.out.println("double");}
public static void main(String args[]) {
f(37);
}
}
which prints the value "float" when you run it.
Suppose that you have another case, with two methods:
f(float, double)
f(double, float)
and a call:
f(37, 47)
Here, the first parameter type of the first method (float)
can be assigned to the first parameter type of the second method
(double). But this doesn't work for the second parameter; you
can't assign double to float. If you start with the second
parameter and go in reverse order, you find the opposite to be
true. That is, the first parameter type of the second method
(double) cannot be assigned to the first parameter type of the
first method (float). But you can assign the second parameter
float to double. So neither of the f methods can be removed from
consideration. Therefore the f(37, 47) call is ambiguous.
In section 15.11.2.2 of the Java Language Specification there's
a short paragraph that helps explain this rule:
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 error.
Another example will help to clarify this rule:
class A {}
class B extends A {}
class C extends B {}
public class OverDemo5 {
static void f(A a) {
System.out.println("f(A)");
}
static void f(B b) {
System.out.println("f(B)");
}
public static void main(String args[]) {
C cref = new C();
f(cref);
}
}
In this example, f(B) is called, because it is more specific than
f(A). Any invocation of f(B) could also be handled by f(A), but the
reverse is not true.
One final point about overload resolution: it's usually a good idea
to avoid being clever with this mechanism, even if you understand it
thoroughly. If you have a case where the parameter types are
completely distinct, as in:
void f(int)
void f(String)
this usage is good, but a case like:
void f(float)
void f(double)
called with f(37) starts to get tricky and confusing.
|