Covariant return in Overriding Methods
In Example 5.2, the definition of the method makeInstance() at (9) overrides the method definition at (3).
// The overridden method in the superclass Light:
public Light makeInstance() { … } // (3) Instance method
// The overriding method in the subclass TubeLight:
@Override
public TubeLight makeInstance() { … } // (9) Overriding instance method at (3).
Note that the method signatures are the same, but the return type at (9) is a subtype of the return type at (3). The method at (9) returns an object of the subtype Tube-Light, whereas the method at (3) returns an object of the supertype Light. This is an example of covariant return.
Depending on whether we call the method makeInstance() on an object of the subtype TubeLight or an object of the supertype Light, the respective method definition will be executed. The code at (18) and (19) illustrates which object is returned by the method, depending on which method definition is executed.
Note that covariant return applies only to reference types, not to primitive types. For example, changing the return type of the energyCost() method at (7) to float will result in a compile-time error. There is no subtype–supertype relationship between primitive types.
Overriding versus Overloading
Method overriding should not be confused with method overloading (§3.6, p. 108). Method overriding always requires the same method signature (name and parameter types) and the same or covariant return types. Overloading occurs when the method names are the same, but the parameter lists differ. Therefore, to overload methods, the parameters must differ in either type, order, or number. As the return type is not a part of the method signature, use of different return types is not sufficient to overload methods. Although methods can be overloaded, as shown at (1) and (2) in Example 5.3, a call to such an overloaded method can be ambiguous, as shown at (3). It is not possible to determine which method should be called, (1) or (2). Disambiguation can be done by making the types of the arguments in the call more specific, as shown at (4) and (5).
Example 5.3 Ambiguous Call to Overloaded Methods
public class Overloader {
static final void callMe(long x, Integer y) { // (1) Overloaded by (2)
System.out.println(“long, Integer”);
}
static void callMe(Integer x, long y) { // (2) Overloaded by (1)
System.out.println(“Integer, long”);
}
public static void main(String[] args) {
// callMe(20, 17); // (3) Ambiguous call: Box 1st or 2nd argument?
callMe(20, Integer.valueOf(17)); // (4) Calls (1): long, Integer
callMe(Integer.valueOf(20), 17); // (5) Calls (2): Integer, long
}
}
Output from the program:
long, Integer
Integer, long
Only non-final instance methods in the superclass that are directly accessible from the subclass using their simple name can be overridden. In contrast, both non-private instance and static methods can be overloaded in the class they are defined in or are in a subclass of the class they are defined in.
Depending on the arguments passed in a call to an overloaded method, the appropriate method implementation is invoked. In Example 5.2, the method energy-Cost() at (2) in superclass Light is overridden in subclass TubeLight at (7) and overloaded at (8). When invoked at (26), the overloaded declaration at (8) is executed.
For overloaded and static methods, which method implementation will be executed at runtime is determined at compile time. In contrast, for overridden methods, the method implementation to be executed is determined at runtime (p. 278).