传入匿名内部类时的空对象

时间:2009-10-28 20:43:50

标签: java inner-classes java1.4

传递最终对象(下面代码中的一个String)时,从匿名内部类打印时,它显示为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中观察到。有关变通方法的任何建议吗?不在现有String上调用toString()是公平的,但由于这只是一个例子,如果我在非String对象上执行它会产生现实世界的影响。

3 个答案:

答案 0 :(得分:5)

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

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

在这些情况下通常就是这种情况,javap -c对于了解javac编译器正在做什么很有用。该规范有助于理解为什么(如果你有足够的耐心)。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

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

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