Java更改变量名称会更改程序行为

时间:2014-07-16 14:06:01

标签: java compiler-construction jvm

我发现在重命名变量后java程序的行为方式不同。我知道这并不是任何人都会使用的实际代码,但如果有人知道最新情况,那么解释会很好。我在Eclipse Kepler上用java 1.6尝试过这个。

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

输出:

  

您好

     

线程中的异常" main" java.lang.NoSuchMethodError:   _test.TestClass $ 1.doStuff()V at _test.TestClass.main(TestClass.java:13)

据我所知,编译器为testClazz $ 1对象创建一个TestClass $ 1.class文件,这会导致命名冲突。

但是在将对象重命名为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");
    }
}

输出结果为:

  

_test.TestClass$1@2e6e1408

     

hello2

有什么想法在这里发生了什么?

3 个答案:

答案 0 :(得分:3)

通过在封闭类的名称后附加$符号和增加的数字来自动命名匿名类。

在您的第一个示例中,anoymous类将被命名为TestClass$1,其中没有doStuff()方法,您只需覆盖toString(),这就是为什么会出现NoSuchMethodError错误

在第二个示例中,您已经有一个名为TestClass$1的局部变量,因此编译器选择的自动生成的名称将是一个不同的名称,很可能是TestClass$2。由于您实例化的TestClass$1不是匿名类,而是由您明确定义的类,因此将实例化具有doStuff()方法的方法,该方法可正确打印"hello2"并且不会覆盖{{ 1}}所以打印其Object.toString()方法返回的值将打印toString()中指定的默认值(这是附加了java.lang.Ojbect符号的类名,后跟默认的哈希码以十六进制格式)。

结论:虽然这是一个有趣的示例,但您绝不应在类名和标识符名称中使用@符号。

答案 1 :(得分:0)

我修改了你的代码以删除" $"从类名中重命名testClass $ 1到t并稍微改变了println,如下所示:

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

现在输出:

t.toString()
TestClass1.doStuff()

这是你期望的吗?

答案 2 :(得分:0)

当类加载器遇到匿名Object() {...}类时,它会以名称TestClass$1加载它。这会与明确定义的class TestClass$1 {...}产生冲突。

但是,类名冲突的处理相当不合理。 This bit of documentation告诉我们

  

如果已经链接了类c,则此方法只返回。

这就是你的情况。您只能加载两个TestClass$1类中的一个。

“不同的变量名称”负责除编译器中的重新编译和重新链接之外的任何其他内容。此时,类加载器可以自由选择两个TestClass$1中最喜欢的一个,并在任何地方使用它。


如果你正在使用像eclipse这样的东西(就像我一样)那么你的字节码将被缓存,直到对源文件进行新的touch操作(并更新时间戳......)。这是我做的重现(在RedHat下运行openjdk 1.7,Eclipse Kepler):

将其放入源文件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 输出:

javaclasses.classes.TestClass$1@42293b53
hello2

在控制台和touch TestClass.java

中打开它

回到eclipse和 ctrl + F11 现在输出:

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

结论:所有可以肯定的说法是,默认的ClassLoader对于手动解析具有相同完全限定名称的类是不可靠的。更改变量名称无关紧要,源文件上更新的时间戳会起作用。