我发现在重命名变量后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
有什么想法在这里发生了什么?
答案 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对于手动解析具有相同完全限定名称的类是不可靠的。更改变量名称无关紧要,源文件上更新的时间戳会起作用。