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

我发现一个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");
    }
}

这输出:

你好

线程“主”java.lang.NoSuchMethodError异常:_test.TestClass $ _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

hello2

有什么想法发生在这里?


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

但是,类名冲突的处理过于不合情理。 这一点文件告诉我们

如果类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对于手动解析具有相同完全限定名的类是不可靠的。 更改变量名称并不重要,源文件上的更新时间戳记会执行此操作。


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

在你的第一个例子中,Anoymous类将被命名为TestClass$1 ,它没有doStuff()方法,你只能覆盖toString() ,这就是为什么你会得到NoSuchMethodError错误。

在你的第二个例子中,你已经有了一个名为TestClass$1的局部变量,所以编译器选择的自动生成的名称将是一个不同的名称,最有可能是TestClass$2 。 既然你实例化了TestClass$1 ,它不是一个匿名类,而是一个你明确定义的类,它将被实例化,它具有一个正确打印"hello2"并且不重写Object.toString()doStuff()方法,由它的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()

这是你期望的吗?

链接地址: http://www.djcxy.com/p/21713.html

上一篇: Java changing variable name changes program behaviour

下一篇: could not find implicit value for evidence parameter of type scalaz.Applicative