Some confusing cases of method overloading

Some time ago I was starting to wonder how the Java compiler actually worked, because some programs I was reading at that time compiled without errors but looked like something was out-of-place, particularly some example programs which included overloaded methods.

Method overloading is basically a feature offered by a programming language allowing to create several methods with the same name, but different parameters type or output type. Java is one of such languages.

Java defines at compile time which method will be actually called when encountered with a method invocation. The rules that are checked by the compiler to take such a decision are rather complex and can be found in the Java Language Specification for the intrigued reader. Those rules may or may not be perfect but are the ones that apply to every Java program, so proposing a change on them produces the very undesirable effect of making every Java program to potentially fail to compile. However, the compiler may be modified to emit a Warning when something looks suspicious, even if the program compiles correctly. In this post I’ll show you some of those cases:

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

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)
  }
}

In this example, the first and second calls are intuitively resolved to what the programmer expect, however the third call is open to some controversy. Since the actual method called by the program is resolved at compile time, the Java compiler checks the actual parameters passed to the method call against its static type, not its dynamic type.

This makes sense since at compile time, the compiler can not determine actually which will be the dynamic type of a variable when the program is running. In the third call, the p2’s static type is A and its dynamic type is B. However the p2 variable is marked as final, meaning it can not change its type during its lifetime. In other words, p2 is instantiated as having the B type, and its dynamic type can not change…ever.

IMO, this seems to me like a programmer error on the intent of the variable p2. It is a B, and it will never by any other type? or it is an A? Because based on that decision the compiler will call one or the other overloaded method. I believe the compiler can check on this and show a Warning. I checked this code compiling against Java 6 Update 20 and no warning, also FindBugs and PMD didn’t detect this as a code smell either.

There are some ways to disambiguate the conflicting line of code:

  • Cast the variable to the more specific type:
    final A p2 = new B();
    o.method((B)p2);
  • Remove the final modifier (meaning that its dynamic type could change):
    A p2 = new B();
    o.method((B)p2);
  • Keep the final modifier, but changing the static type to one that indeed lead the compiler to resolve to one (and only one) matching method:
    final B p2 = new B();
    o.method(p2);
    // or
    final A p2 = new A();
    o.method(p2);

Dealing with blank final declarations

Consider the following code excerpt:

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

There is no obvious way of detecting the issue here (at least not without a flow analysis) so a conservative thing to do is to raise the warning anyway, so the programmer may take out either the final keyword, or refactor the code like this:

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

This time, PMD somewhat points to a problem here, since it warns about the use of final (blank) local variables (AvoidFinalLocalVariable Rule.)

The conditional ? operator

Seems counter intuitive that if

o.method(new B());

call method(B), the following two actually resolve to 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());

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

As both the second and third operand of the ? operator are by all means constant expressions, IMO they should be treated just with the same semantics as if a blank final variable had been used instead:

final A a;
if (condition)
  a = expressionReturningA;
else
  a = expressionReturningB;
o.method(a);

Then, the same reasoning as before applies.

IMO, using the ? operator to resolve an expression for use as an actual parameter in an overloaded method call should be avoided if possible, it’s a code smell, and as such, tools must warn the programmer. Since the ? operator has only one type (which depends on the second and third operand), the only case when it may not emit a warning is if both operands reduce to a type that uniquely resolve to only one (and the same) method.

Conclusions

The interaction between overloaded methods and different kind of expressions in Java is tricky. On the one hand, method resolution rules are very complicated, but reasoning about expressions that represent constants or non changing values (such as final variables) should allow the programmer to think about the code in abstract terms, just as any mathematical formula would behave. Why bother to program in a strongly typed language if we can not reason about the program in an intuitive fashion? Why not make the tools to emit a warning when we write somewhat ambiguous code? Why can’t we use the Java type system to the fullest?

In the upcoming weeks I’ll post some more obscure examples.

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

One thought on “Some confusing cases of method overloading

Leave a Reply

Your email address will not be published. Required fields are marked *