Some corner cases of overloading to consider (original) (raw)

Gabriel Belingueres belingueres at gmail.com
Tue Mar 24 10:32:54 PDT 2009


Hi,

This is a document I prepared to show some uses of overloading that IMO may well be candidate for the compiler to show a Warning (maybe when using an Xlint option), instead of silently compile.

If there is enough interest, I could prepare a more formal proposal for the issues here presented.

Regards, Gabriel

  1. Calling an overloaded method where the actual parameter is a final variable

Example:

class A {}

class B extends A {}

public class OverloadingTest {

public void method(A a) {
    System.out.println("method(A)");
}

public void method(B b) {
    System.out.println("method(B)");
}

public static void main(String[] args) {
    OverloadingTest o = new OverloadingTest();

    A p = new B();
    o.method(p); // call method(A)

    o.method(new B()); // call method(B)

    final A p2 = new B();
    o.method(p2);  // call method(A)
}

}

The 3rd. call to the overloaded method is calling method(A) which is correct because the compile time type of variable p2 is A. However, since the p2 variable is declared as a final variable, it means that it can not change its runtime type, but since p2 is actually a B, and the runtime type can never change, why it is not treated in a similar way to o.method(new B())?

This might be a sign that the programmer made a mistake, and the compiler should emit a Warning indicating so.

How to disambiguate: a) cast to a more specific type: final A p2 = new B(); o.method((B)p2); b) removing the final modifier, indicating that this specific variable can change its runtime type in its lifetime, and should call method(A), as current overloading rules: A p2 = new B(); o.method(p2); c) keeping the final modifier, but changing the compile time type to B, indicating that it should call method(B), as current overloading rules: final B p2 = new B(); o.method(p2); or final A p2 = new A(); o.method(p2);

1.1) Dealing with blank final declarations

Example: final A a; ... if (someCondition) { ... a = new A(); ... } else { ... a = new B(); ... } ... method(a);

There is no obvious way of detecting the issue here (without a flow analysis at least). So a safest thing can be to emit the warning anyway, so the programmer could refactor to something like this: ... if (someCondition) { ... final A a = new A(); ... method(a); ... } else { ... final B b = new B(); ... method(b); ... }

1.2) dealing with the conditional ? operator

Seems counter intuitive that if o.method(new B()); calls method(B), the following two will call method(A): o.method((false) ? new A() : new B()); o.method((false) ? (A)null : new B());

and the following will call method(B): o.method((false) ? null : new B()); // prints method(B)

This is because of the resulting expression type produced by the ? operator.

IMO, then conditional operator ? result should be considered as if assigned to a final variable:

example: o.method((condition) ? expressionReturningA : expressionReturningB);

should produce the same semantics as: final A a; if (condition) a = expressionReturningA; else a = expressionReturningB; o.method(a);

which would show a Warning since a final variable is present (as in the previous section.)

What happens when one of the 2nd or 3rd operand of ? is null? o.method((condition) ? null : expressionReturningB);

IMO this case should not show a Warning, since both operands resolve to call method(B) (because calling o.method(null) would resolve to the most specific type)

In the third example the second operand is null, so the resulting type is B (because of the third rule of the ? operand definition), BUT when applying the 2) rule, a warning is emitted.

Specifically, IMO currently using the ? operator as a expression for an overloaded method should be avoided if possible, given that the current definition of the ? operator has only one type (which is dependent of the 2nd and 3rd operand). Instead, when detecting the ? operator as a parameter of an overloaded method, BOTH the 2nd and 3rd operand types should reduce to a type which uniquely identify only one possible method to call.

  1. Calling a method which actual parameter is a null literal

Example (from book Java Puzzlers):

public class Confusing {

private Confusing(Object o) {
    System.out.println("Object");
}

private Confusing(double[] dArray) {
    System.out.println("double array");
}

public static void main(String[] args) {
    new Confusing(null);
}

}

this compiles without warning, but because the null literal has no defined compile time type IMO it is ambiguous. A cast should be used to disambiguate, otherwise a Warning should be emitted by the compiler.

  1. Calling a method which actual parameter can be autoboxed/unboxed

Example: public class OverloadPrimitiveTest {

public void method(int a) {
    System.out.println("method(int)");
}

public void method(Integer b) {
    System.out.println("method(Integer)");
}

public static void main(String[] args) {
    OverloadPrimitiveTest o = new OverloadPrimitiveTest();

    o.method(1); // call method(int)
    o.method(new Integer(1)); // call method(Integer)
    o.method((short) 1); // call method(int)
    o.method(null); // call method(Integer)

    o.method((true) ? 1: new Integer(2)); // call method(int), ambiguous
}

}

This is similar to what was described in point 1.2), only the ? operator's are of types which can be autoboxed/unoboxed to produce the other type. Again it is counter intuitive and a Warning should be emitted.

It can be detected using the same rule for the ? operator given in 1.2), which take into account both operand types to determine if it uniquely identify the overloaded method to call. In this case, the 2nd operand type is int, and the 3rd operand type is Integer, which both are valid argument types for the same overloaded method. Another solution for detection could be just emit a warning when the compiler encounters that both method(int) and method(Integer) are identified for calling, regardless of if the current parameter type used in the method call is boxed or primitive.

  1. Calling a method with generic argument

Example:

public class GenericOverloadTest {

public void method(T t) { System.out.println("method(T)"); }

public void method(int n) { System.out.println("method(int)"); }

public static void main(String[] args) { GenericOverloadTest o = new GenericOverloadTest(); o.method(1); // call method(int) o.method(new Integer(1)); // call method(T) o.method((short) 1); // call method(int) o.method(null); // call method(T)

o.method((true) ? 1: new Integer(2)); // call method(int), ambiguous

}

}

This is similar to 3) but here the overloaded method is dependent of the generic type that was used. A Warning should be emitted too.



More information about the coin-dev mailing list