Wednesday, 19 September 2012

Puzzle 71: Import Duty


In release 5.0, the Java platform introduced a number of facilities that make it easier to work with arrays. This program uses varargs, autoboxing, static import (see http://java.sun.com/j2se/5.0/docs/guide/language [Java-5.0]) and the convenience method Arrays.toString (see Puzzle 60). What does the program print?
import static java.util.Arrays.toString;



class ImportDuty {

    public static void main(String[] args) {

        printArgs(1, 2, 3, 4, 5);

    }



    static void printArgs(Object... args) {

        System.out.println(toString(args));

    }

}


Solution 71: Import Duty

You might expect the program to print [1, 2, 3, 4, 5], and indeed it would, if only it compiled. Sadly, the compiler just can't seem to find the right toString method:
ImportDuty.java:9:Object.toString can't be applied to (Object[])

System.out.println(toString(args));

                   ^


Is the compiler just being dense? Why would it try to apply Object.toString(), which doesn't match the call's parameter list, when Arrays.toString(Object[]) matches perfectly?
The first thing the compiler does when selecting a method to be invoked at run time is to choose the scope in which the method must be found [JLS 15.12.1]. The compiler chooses the smallest enclosing scope that has a method with the right name. In our program, this scope is the class ImportDuty, which contains the toString method inherited from Object. This scope has no applicable method for the invocation toString(args), so the compiler must reject the program.
In other words, the desired toString method isn't in scope at the point of the invocation. The imported toString method is shadowed by a method with the same name inherited into ImportDuty from Object [JLS 6.3.1]. Shadowing is a lot like obscuring (Puzzle 68). The key distinction is that a declaration can shadow another declaration only of the same kind: One type declaration can shadow another, one variable declaration can shadow another, and one method declaration can shadow another. By contrast, variable declarations can obscure type and package declarations, and type declarations can obscure package declarations.
When one declaration shadows another, the simple name refers to the entity in the shadowing declaration. In this case, toString refers to the toString method from Object. Simply put, members that are naturally in scope take precedence over static imports. One consequence is that static methods with the same name as Object methods cannot be used with the static import facility.
Since you can't use static import with Arrays.toString, use a normal import declaration instead. This is the way Arrays.toString was meant to be used:
import java.util.Arrays;

class ImportDuty {

    static void printArgs(Object... args) {

        System.out.println(Arrays.toString(args));

    }

}


If you are desperate to avoid qualifying Arrays.toString invocations explicitly, you can write your own private static forwarding method:
private static String toString(Object[] a) {

    return Arrays.toString(a);

}


The static import facility was intended for situations in which static members of another class are used repeatedly, and qualifying each use would seriously clutter a program. In such situations, the static import facility can significantly enhance readability. It is far safer than implementing interfaces to inherit their constants, which you should never do [EJ Item 17]. Overuse of the static import facility can, however, harm readability by making the class of a static member unclear at the point of use. Use the static import facility sparingly and only when there is a compelling need.
For API designers, be aware that the static import facility cannot be used effectively on a method if its name is already in scope. This means that static import can seldom be used on static methods that share names with methods in common interfaces, and it can never be used on static methods that share names with methods found in Object. Once again, this puzzle demonstrates that name reuse other than overriding is generally confusing. We have seen this with overloading, hiding, and obscuring, and now we see it with shadowing

No comments:

Post a Comment

Your comments are welcome!