Changing the name of a Java variable changes the behavior of the program

I found a scenario where a java program behaves differently after renaming a variable. I understand that this is not really code that anyone would use, but if someone knows what is going on, it would be nice to get 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"); } } 

It is output:

Hi

An exception in the thread "main" java.lang.NoSuchMethodError: _test.TestClass $ 1.doStuff () V in _test.TestClass.main (TestClass.java:13)

As I understand it, the compiler creates the TestClass $ 1.class file for the testClazz $ 1 object, and this causes a name clash.

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"); } } 

Output:

_test.TestClass$1@2e6e1408

hello2

Any ideas what is going on here?

+7
java compiler-construction jvm
source share
3 answers

When the classloader encounters the anonymous class Object() {...} , 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 fairly shamelessly. This bit of documentation tells us that

If class c is already bound, then this method simply returns.

What happens in your case. You load only one of the two TestClass$1 classes.

The "names of different variables" are not responsible for anything other than re-compiling and re-linking inside your compiler. At this point, the class loader can choose which of the two TestClass$1 better and use it everywhere.


If you use something like eclipse (like me), your bytecode will be cached until a new touch operation in the source file (and timestamp updates ...). Here is what I did to reproduce (running openjdk 1.7, Eclipse Kepler under RedHat):

Put this in the 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 it in the console and touch TestClass.java

Go back to eclipse and ctrl + F11 now displays:

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

Inclusion: all that can be said definitively is that the ClassLoader class is by default unreliable for manually resolving classes with the same full names. Changing variable names does not matter, updated timestamp in source file.

0
source share

Anonymous classes are assigned automatically by adding the $ sign and an increasing number to the name of the enclosing class.

In your first example, the anoymous class will be called TestClass$1 , which does not have a doStuff() method, you only override toString() , why you get a NoSuchMethodError error.

In your second example, you already have a local variable named TestClass$1 , so the auto- TestClass$1 name chosen by the compiler will have a different name, most likely TestClass$2 . Since you are creating an instance of TestClass$1 , which is not an anonymous class, but an explicitly defined class that you will create, which has a doStuff() method that correctly prints "hello2" and which does not override Object.toString() , so printing returns values ​​using the toString() method will print the default value specified in java.lang.Ojbect (which is the class name added with the @ sign, followed by the default hash code in hexadecimal format).

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

+3
source share

I changed your code to remove "$" from the class names and renamed testClass $ 1 to t and slightly changed println 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()"); } } 

Exit now:

 t.toString() TestClass1.doStuff() 

Is that what you expect?

0
source share

All Articles