Some corner cases of overloading to consider

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.


2) 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.


3) 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.


4) Calling a method with generic argument

Example:

public class GenericOverloadTest<T> {

  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<Integer> o = new GenericOverloadTest<Integer>();
    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