Java changing variable name changes program behaviour

I found a scenario where java program behaves differently after renaming a variable. I understand this isn't actually code that anyone would use but if someone knows whats going on it would be nice to have an explanation. I tried this with java 1.6 on Eclipse Kepler.

package _test;

public class TestClass{
    public static void main(String...args){
        Object testClazz$1 = new Object (){
            public String toString() {
                return "hello";
            }
        };
        TestClass$1 test = new TestClass$1();
        System.out.println(testClazz$1.toString());
        test.doStuff();
    }
}

class TestClass$1{
    public void doStuff(){
        System.out.println("hello2");
    }
}

This outputs:

hello

Exception in thread "main" java.lang.NoSuchMethodError: _test.TestClass$1.doStuff()V at _test.TestClass.main(TestClass.java:13)

As far as I understand the compiler creates a TestClass$1.class file for the testClazz$1 object and this causes a naming collision.

But after renaming the object to testClass$1:

package _test;

public class TestClass{
    public static void main(String...args){
        Object testClass$1 = new Object (){
            public String toString() {
                return "hello";
            }
        };

        TestClass$1 test = new TestClass$1();
        System.out.println(testClass$1.toString());
        test.doStuff();
    }
}

class TestClass$1{
    public void doStuff(){
        System.out.println("hello2");
    }
}

The output is:

_test.TestClass$1@2e6e1408

hello2

Any ideas what is going on here?


When the classloader encounters the anonymous Object() {...} class it loads it under the name TestClass$1 . This creates a conflict with class TestClass$1 {...} which was explicitly defined.

However, class name conflicts are handled rather ungracefully. This bit of documentation tells us that

If the class c has already been linked, then this method simply returns.

That's what happens in your case. You only ever load one of the two TestClass$1 classes.

The "different variable names" are not responsible for anything other than re-compiling and re-linking within your compiler. At this point, the classloader is free to pick whichever one of the two TestClass$1 likes better and use that everywhere.


If you're using something like eclipse (like I am) then your bytecode will get cached, until a new touch operation on the source file (and updating of timestamps...). Here's what I did to reproduce (running openjdk 1.7, Eclipse Kepler under RedHat):

Put this inside a source file TestClass.java :

package javaclasses.classes;

public class TestClass{
    public static void main(String...args){
        Object o = new Object (){
            public String toString() {
                return "hello";
            }
        };

        TestClass$1 test = new TestClass$1();
        System.out.println(o.toString());
        test.doStuff();
    }
}

class TestClass$1{
    public void doStuff(){
        System.out.println("hello2");
    }
}

ctrl + F11 outputs:

javaclasses.classes.TestClass$1@42293b53
hello2

Open this in a console and touch TestClass.java

Go back in eclipse and ctrl + F11 now outputs:

hello
Exception in thread "main" java.lang.NoSuchMethodError: javaclasses.classes.TestClass$1.doStuff()V
    at javaclasses.classes.TestClass.main(TestClass.java:13)

Conlusion: All that can be said definitively is that the default ClassLoader is unreliable for manually resolving classes with the same fully qualified names. Changing variable names doesn't matter, the updated timestamp on your source file does.


Anonymous classes are named automatically by appending a $ sign and an increasing number to the name of the enclosing class.

In your first example the anoymous class will be named TestClass$1 which has no doStuff() method, you only override toString() that's why you get NoSuchMethodError error.

In your 2nd example you already have a local variable named TestClass$1 , so the auto-generated name chosen by the compiler will be a different name, most likely TestClass$2 . Since you instantiate TestClass$1 which is not an anonymous class but a class explicitly defined by you, that will be instantiated which has a doStuff() method which properly prints "hello2" and which does not override Object.toString() so printing the value returned by its toString() method will print the default value as specified in java.lang.Ojbect (which is the class name appended with a @ sign followed by the default hash code in hexadecimal format).

Conclusion: While this is an interesting example, you should never use the $ sign in your class names and in identifier names.


I modified your code to remove the "$" from the class names and renamed testClass$1 to t and changed the println slightly as follows:

public class TestClass{

    public static void main(String...args){
        Object t = new Object (){
            public String toString() {
                return "t.toString()";
            }
        };
        TestClass1 tc1 = new TestClass1();
        System.out.println(t.toString());
        tc1.doStuff();
    }
}

class TestClass1{
    public void doStuff(){
        System.out.println("TestClass1.doStuff()");
    }
}

Output is now:

t.toString()
TestClass1.doStuff()

Is this what you expect?

链接地址: http://www.djcxy.com/p/21714.html

上一篇: 使用Java将嵌套的任意JSON转换为CSV

下一篇: Java更改变量名称会更改程序行为