Wednesday, 19 September 2012

Puzzle 67: All Strung Out


One name can be used to refer to multiple classes in different packages. This program explores what happens when you reuse a platform class name. What do you think it does? Although this is the kind of program you'd normally be embarrassed to be seen with, go ahead and lock the doors, close the shades, and give it a try:
public class StrungOut {

    public static void main(String[] args) {

        String s = new String("Hello world");

        System.out.println(s);

    }

}



class String {

    private final java.lang.String s;

    

    public String(java.lang.String s) {

        this.s = s;

    }

    

    public java.lang.String toString() {

        return s;

    }

}


Solution 67: All Strung Out

This program looks simple enough, if a bit repulsive. The class String in the unnamed package is simply a wrapper for a java.lang.String instance. It seems the program should print Hello world. If you tried to run the program, though, you found that you could not. The VM emits an error message something like this:
Exception in thread "main" java.lang.NoSuchMethodError: main


But surely there is a main method: It's right there in black and white. Why can't the VM find it?
The VM can't find the main method because it isn't there. Although StrungOut has a method named main, it has the wrong signature. A main method must accept a single argument that is an array of strings [JVMS 5.2]. What the VM is struggling to tell us is that StrungOut.main accepts an array of our String class, which has nothing whatsoever to do with java.lang.String.
If you really must write your own string class, for heaven's sake, don't call it String. Avoid reusing the names of platform classes, and never reuse class names from java.lang, because these names are automatically imported everywhere. Programmers are used to seeing these names in their unqualified form and naturally assume that these names refer to the familiar classes from java.lang. If you reuse one of these names, the unqualified name will refer to the new definition any time it is used inside its own package.
To fix the program, simply pick a reasonable name for the nonstandard string class. The following version of the program is clearly correct and much easier to understand than the original. It prints Hello world just as you'd expect:
public class StrungOut {

    public static void main(String[] args) {

        MyString s = new MyString("Hello world");

        System.out.println(s);

    }

}

class MyString {

    private final String s;

    

    public MyString(String s) { this.s = s; }

    

    public String toString() { return s; }

}


Broadly speaking, the lesson of this puzzle is to avoid the reuse of class names, especially Java platform class names. Never reuse class names from the package java.lang. The same lesson applies to library designers. The Java platform designers slipped up a few times. Notable examples include java.sql.Date, which conflicts with java.util.Date, and org.omg.CORBA.Object. As in many other puzzles in this chapter, the lesson is a specific case of the principle that you should avoid name reuse, with the exception of overriding. For platform implementers, the lesson is that diagnostics should make clear the reason for a failure. The VM could easily have distinguished the case where there is no main method with the correct signature from the case where there is no main method at all.

No comments:

Post a Comment

Your comments are welcome!