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

Java更改变量名会改变程序行为,java,compiler-construction,jvm,Java,Compiler Construction,Jvm,我发现了一个场景,java程序在重命名变量后的行为有所不同。我知道这实际上不是任何人都会使用的代码,但如果有人知道发生了什么,最好有一个解释。我在EclipseKepler上用Java1.6尝试了这一点 package _test; public class TestClass{ public static void main(String...args){ Object testClazz$1 = new Object (){ public S

我发现了一个场景,java程序在重命名变量后的行为有所不同。我知道这实际上不是任何人都会使用的代码,但如果有人知道发生了什么,最好有一个解释。我在EclipseKepler上用Java1.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位于_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

你好


你知道这是怎么回事吗

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

在第一个示例中,异常类将命名为
TestClass$1
,它没有
doStuff()
方法,您只重写
toString()
,这就是为什么会得到
NoSuchMethodError
错误

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


结论:虽然这是一个有趣的例子,但千万不要在类名和标识符名中使用
$
符号。

我修改了您的代码,从类名中删除了“$”,并将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()

这就是您所期望的吗?

当类加载器遇到匿名
对象(){…}
类时,它会以
TestClass$1
的名称加载它。这会与显式定义的
类TestClass$1{…}
产生冲突

然而,类名冲突的处理相当不公平。告诉我们

如果类c已经被链接,那么这个方法只返回

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

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


如果您使用的是eclipse之类的东西(比如我),那么字节码将被缓存,直到源文件上有一个新的
touch
操作(以及更新时间戳…)。下面是我复制的内容(运行OpenJDK1.7,在RedHat下运行Eclipse开普勒):

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

在控制台中打开它,然后触摸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)


结论:可以肯定地说,默认类加载器对于手动解析具有相同完全限定名的类是不可靠的。更改变量名并不重要,源文件上的更新时间戳会起作用。

不确定发生这种情况的确切原因,但需要注意的是命名:使用$符号命名变量是不好的做法,因为这通常是为系统生成的变量保留的。我敢打赌,之所以会发生这种奇怪的行为,是因为您被某些系统生成的TestClass绊倒了。系统创建了一个匿名类TestClass$1,但发生这种情况仍然很奇怪。“应该只在机械生成的源代码中使用$character,或者,很少用于访问遗留系统上预先存在的名称。”引用《我只是对jvm如何处理命名冲突感兴趣》一文,使用$sign-in类和变量名没有任何合理的理由。
hello
Exception in thread "main" java.lang.NoSuchMethodError: javaclasses.classes.TestClass$1.doStuff()V
    at javaclasses.classes.TestClass.main(TestClass.java:13)