Tuesday, 18 September 2012

Puzzle 46: The Case of the Confusing Constructor


This puzzle presents you with two Confusing constructors. The main method invokes a constructor, but which one? The program's output depends on the answer. What does the program print, or is it even legal?
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);

    }

}


Solution 46: Case of the Confusing Constructor

The parameter passed to the constructor is the null object reference, so at first glance, it seems that the program should invoke the Object overloading and print Object. On the other hand, arrays are reference types too, so null could just as well apply to the double[] overloading. You might therefore conclude that the call is ambiguous, which suggests that the program shouldn't compile. If you tried running the program, you found that neither of these intuitions is correct: The program prints double array. This behavior may seem perverse, but there is a good reason for it.
Java's overload resolution process operates in two phases. The first phase selects all the methods or constructors that are accessible and applicable. The second phase selects the most specific of the methods or constructors selected in the first phase. One method or constructor is less specific than another if it can accept any parameters passed to the other [JLS 15.12.2.5].
In our program, both constructors are accessible and applicable. The constructor Confusing(Object) accepts any parameter passed to Confusing(double[]), so Confusing(Object) is less specific. (Every double array is an Object, but not every Object is a double array.) The most specific constructor is therefore Confusing(double[]), which explains the program's output.
This behavior makes sense if you pass a value of type double[]; it is counterintuitive if you pass null. The key to understanding this puzzle is that the test for which method or constructor is most specific does not use the actual parameters: the parameters appearing in the invocation. They are used only to determine which overloadings are applicable. Once the compiler determines which overloadings are applicable and accessible, it selects the most specific overloading, using only the formal parameters: the parameters appearing in the declaration.
To invoke the Confusing(Object) constructor with a null parameter, write new Confusing((Object)null). This ensures that only Confusing(Object) is applicable. More generally, to force the compiler to select a specific overloading, cast actual parameters to the declared types of the formal parameters.
Selecting among overloadings in this fashion is unpleasant. In your APIs, ensure that clients aren't forced to go to these extremes. Ideally, you should avoid overloading: Use different names for different methods. Sometimes, this is not possible. Constructors don't have names, so they can't be given different names. You can, however, alleviate the problem by making constructors private and providing public static factories [EJ Item 1]. If constructors have many parameters, you can reduce the need for overloading with the Builder pattern [Gamma95].
If you do overload, ensure that all overloadings accept mutually incompatible parameter types, so that no two are applicable at the same time. Failing that, ensure that all applicable overloadings have the same behavior [EJ Item 26].
In summary, overload resolution can be confusing. Avoid overloading where possible. If you must overload, obey the guidelines outlined here to minimize confusion. If a poorly designed API forces you to select among overloadings, cast actual parameters to the types of the formal parameters of the desired overloading.

No comments:

Post a Comment

Your comments are welcome!