2

当传递一个最终对象(下面代码中的字符串)时,它在从匿名内部类打印时显示为 null。但是,当传入最终值类型或直接最终字符串时,它的值会正确显示。在匿名内部类的上下文中真正意味着什么final以及为什么对象传递为 null?

public class WeirdInners
{
    public class InnerThing
    {
        public InnerThing()
        {
            print();
        }

        public void print(){

        }
    }

    public WeirdInners()
    {
        final String aString = "argh!".toString();
        final String bString = "argh!";
        System.out.println(aString);
        System.out.println(bString);


        InnerThing inner =new InnerThing(){
            public void print()
            {
                System.out.println("inner"+aString); // When called from constructor, this value is null.
                System.out.println("inner"+bString); // This value is correctly printed.
            }
        };

        inner.print();
    }


    public static void main(String[] args)
    {
        WeirdInners test1 = new WeirdInners();
    }

}

这对我来说是非常奇怪的行为,因为期望 String一个对象,为什么调用toString()会改变事情?

其他信息:此行为仅在使用 Java 1.4 时观察到,而不是在 Java 5 中。关于解决方法的任何建议?不调用toString()现有的 String 是很公平的,但由于这只是一个示例,如果我在非 String 对象上执行它,它就会对现实世界产生影响。

4

3 回答 3

5

如果您检查compile-time constantsJLS 中的部分,您会发现调用.toString()确实有所作为。就像前缀一样的废话false?null+"":

这里重要的是设置关闭的字段和构造函数的相对顺序。如果您使用-target 1.4或更高版本(这不是 1.4 中的默认设置!),则将在调用 super 之前复制这些字段。对于 1.3 之前的规范,这是非法的字节码。

在这些情况下,通常情况下,javap -c查看 javac 编译器在做什么很有用。该规范有助于理解原因(如果您有足够的耐心)。

于 2009-10-28T21:20:38.120 回答
0

我的猜测是,当 InnerThing() 的构造函数(隐式)将其传递this给 InnerThing 的匿名子类的 print 方法而对象未完全构造时,您会触发未定义的行为。这this反过来又依赖于对thisWierdInners 的隐式引用。

调用.toString()将 aString 的初始化从编译时移动到运行时。为什么 Java 1.4 和 1.5 之间的未定义行为不同可能是 JVM 实现细节。

于 2009-10-28T21:21:56.557 回答
0

从超类构造函数调用重写的方法是危险的,因为它们将在对象的子类部分初始化之前被调用。

此外,访问封闭范围的最终变量的内部类实际上将访问这些变量的副本(这就是为什么它们需要是最终的),并且这些复制的字段驻留在匿名子类中。

我怀疑bString区别对待的原因是它的值在编译时是已知的,这允许编译器内联子类中的字段访问,从而使该字段的初始化时间无关紧要。

于 2009-10-28T21:29:07.403 回答