【问题标题】:Null Object When Passed Into An Anonymous Inner Class传入匿名内部类时的空对象
【发布时间】:2009-10-28 20:43:49
【问题描述】:

当传递一个最终对象(下面代码中的字符串)时,它在从匿名内部类打印时显示为 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();
    }

}

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

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

【问题讨论】:

  • 您在 Java 1.4 下看到了什么结果?我正在使用 Java 6,我得到了(可能无法很好地格式化并且没有 cmets 的预览):啊!啊!内拉!内拉!内拉!内拉!并不是说它对您有任何帮助,但 Java 1.4 的 EOL 通知于 2006 年 12 月宣布,而 Java 1.4 的 EOSL 是一年前的这个星期五。 Java 5 的 EOL 通知于 2008 年 4 月发布,并于本周五达到其 EOSL。您有机会升级到 Java 6 吗? java.sun.com/products/archive/eol.policy.html
  • 您看到了预期的输出。使用 "argh!".toString(); 时 1.4 的行为是 innernull。

标签: java inner-classes java1.4


【解决方案1】:

如果您查看 JLS 中有关 compile-time constants 的部分,您会发现调用 .toString() 确实有所作为。像前缀false?null+"":这样的废话也是如此。

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-14
        • 1970-01-01
        • 2014-05-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多