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