Tuesday, 18 September 2012

Puzzle 48: All I Get Is Static


The following program models the behavioral difference between Basenjis and other dogs. In case you didn't know, the Basenji is a breed of small, curly-tailed dogs of African origin that do not bark. What does the program print?
class Dog {

    public static void bark() {

        System.out.print("woof ");

    }

}



class Basenji extends Dog {

    public static void bark() { }

}



public class Bark {

    public static void main(String args[]) {

        Dog woofer = new Dog();

        Dog nipper = new Basenji();

        woofer.bark();

        nipper.bark();

    }

}


Solution 48: All I Get Is Static

On casual inspection, it would appear that this program should just print woof. After all, Basenji extends Dog and defines its bark method to do nothing. The main method invokes the bark method, first on woofer the Dog and again on nipper the Basenji. Basenjis don't bark, but apparently this one does. If you ran the program, you found that it prints woof woof. What is the matter with poor Nipper?
The title of this puzzle gives a big hint. The problem is that bark is a static method, and there is no dynamic dispatch on static methods [JLS 15.12.4.4]. When a program calls a static method, the method to be invoked is selected at compile time, based on the compile-time type of the qualifier, which is the name we give to the part of the method invocation expression to the left of the dot. In this case, the qualifiers of the two method invocations are the variables woofer and nipper, both of which are declared to be of type Dog. Because they have the same compile-time type, the compiler causes the same method to be invoked: Dog.bark. This explains why the program prints woof woof. It doesn't matter that the runtime type of nipper is Basenji; only its compile-time type is considered.
To fix this program, simply remove the static modifier from the two bark method declarations. Then the bark method in Basenji will override rather than hide the bark method in Dog, and the program will print woof instead of woof woof. With overriding, you get dynamic dispatch; with hiding, you don't.
When you invoke a static method, you typically qualify it with a class rather than an expression: for example Dog.bark or Basenji.bark. When you read a Java program, you expect classes to be used as the qualifiers for static methods, which are statically dispatched, and expressions to be used as the qualifiers for instance methods, which are dynamically dispatched. Coupled with the different naming conventions for classes and variables, this provides a strong visual cue as to whether a given method invocation is static or dynamic. The program in this puzzle uses an expression as the qualifier for a static method invocation, which is misleading. Never qualify a static method invocation with an expression.
The confusion is compounded by the appearance of overriding. The bark method in Basenji has the same signature as the one in Dog. That is the usual formula for overriding, which suggests dynamic dispatch. In this case, however, the methods are declared static. Static methods cannot be overridden; they can only be hidden, and just because you can doesn't mean you should. To avoid confusion, do not hide static methods. There is nothing to gain, and much to lose, from reusing the name of a superclass's static method in a subclass.
The lesson for language designers is that invocations of class and instance methods should look different from each other. One way to further this goal is to disallow the use of expressions as qualifiers for static methods. A second way to distinguish static and instance method invocations is to use different operators, as C++ does. A third alternative is to finesse the issue by dispensing with the concept of static methods altogether, as Smalltalk does.
In summary, qualify static methods invocations with a class name, or don't qualify them at all if you're invoking them from within their own class, but never qualify them with an expression. Also, avoid hiding static methods. Together, these guidelines help eliminate the misleading appearance of overriding with dynamic dispatch for static methods

No comments:

Post a Comment

Your comments are welcome!