Some confusing cases of method overloading – Part 3

Following this series of posts related to method overloading in Java, now we will see how will the compiler behave when calling an overloaded method which actual parameter is a result of the conditional ? operator.

First of all, in the case you missed the previous posts, here you have the links to the first part, and the second part.

Let’s consider the following example program:

import java.util.Random;

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)

    Random random = new Random();
    o.method((random.nextBoolean()) ? 1 : new Integer(2)); // call method(int), ambiguous
  }

}

The first two calls are straightforward to digest, they call the intuitively correct overloaded method since their actual parameters in the method call expression are those specific ones.

The third call seems strange at first sight but it is OK, since Java can safely widen a primitive short to an int (no overflow can happen), so this resolve to the overloaded method which takes a primitive int parameter. The fourth call is very intuitive also, because a null value can be assigned to reference types, not a primitive type, so using a null as the actual parameter resolves intuitively to the method that takes an Integer object.

The fifth call is what I consider ambiguous.

Why is it ambiguous?

The last call does a couple of things with its actual parameter. First of all, the actual parameter is taken after the evaluation of the conditional ? operator, and the type returned by the conditional operator is the primitive int, because the specification says that it will unbox its second and third operand as necessary (JLS 15.25.)

However, comparing the fifth invocation of the overloaded method with the first and second ones, it is confusing at first sight which one of the methods will be selected at compile-time.

An example using references

The same confusing case may happen with references to objects too, since it is not necessary the presence of a boxing or unboxing operation for the anomalous case to appear. Just like the primitive int type can be widened to long or float, a reference type can be widened to some superclass in the class herarchy, or some implemented interface. Here is an example:

import java.applet.Applet;
import java.awt.Panel;
import java.util.Random;

import javax.swing.JApplet;

public class OverloadPrimitiveTest2 {

 public void method(Panel p) {
   System.out.println("Panel");
 }

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

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

   o.method(new Panel()); // prints Panel
   o.method(new JApplet()); // prints JApplet

   o.method((new Random().nextBoolean()) ? new Applet() : new JApplet()); // prints Panel
 }

}

Here, JApplet is a subclass of Applet, which in turn is a subclass of Panel. In the third method invocation the conditional ? operator decides that the type of the expression is Applet, since JApplet is widened to Applet without any problem. In turn, the best fit for the method invocation is calling method(Panel) since an expression of type Applet can be widened to type Panel. Again it is confusing the intention of the programmer since both methods may have been selected (if not for the conditional ? operator.)

A note on code checking tools

The usual code analysis tools, like FindBugs, PMD, and (now free) Google’s CodePro AnalytiX are happy to detect and mark the use of the conditional ? operator. I find that informative but more often than not, it marks a false positive, that is to say that not necessarily I believe that using the conditional ? operator is wrong: it has its use to make the program somewhat easier to read. However, I do think that the conditional ? operator is too smart in trying to reduce the two evaluated expressions into a single type expression: sometimes it widens too much and useful type information is lost in the oblivion.

As a side note, CodePro AnalytiX has a rule indicating that there is an overloaded method and recommends that either rename the method (to avoid the overloading) or change the number of parameters. I actually didn’t find this recommendation very useful, as the intention of an overloaded method is actually that by design it should have the same name (because they perform the same process for a different type) and it would by unnatural to add an extra parameter just for disambiguate the method invocation at compile-time.

Conclusions

The conditional ? operator is tricky, because it is too smart and infer things that most of the time we will not be aware of. In addition, combine method overloading with this operator and we will certainly have some fun time hunting bugs. However, I don’t personally think nor that the conditional ? operator should be avoided (as suggested by code analysis tools) nor method overloading, though I do believe you must not mix them.

You can distill a rule from this. Note that in the conditional ? operator, the types of the second and third operands are different and when considered in isolation, each one of them would make the compiler choose a different overloaded method to invoke. So a simple rule of thumb to remember would be:

Don’t use the conditional ? operator as an actual parameter of an overloaded method if the second and third operands have different types.

I wish some day I could see a rule implemented on those code analysis tools to detect this tricky cases.

Leave a Reply

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