带有覆盖方法的NullPointerException

时间:2009-06-08 18:08:57

标签: java

有人可以就解决以下问题的最佳方法发表意见吗?我已经在构造函数中尝试了init块并初始化了字段,但是在重写方法尝试清除它之前都没有调用它。

 class TestRunner {
        public static void main(String[] args) {
            ftw ftw = new ftw();
            ftw.dontPanic();
        }
    }

    class wtf {

        wtf(){
            this.dontPanic();
        }

        void dontPanic() {
            System.out.println("super don't panic");
        }
    }

    class ftw extends wtf {
        final HashSet variableOfDoom = new HashSet();

        ftw(){
            super();
        }

        void dontPanic() {
            super.dontPanic();
            System.out.println("sub don't panic");
            variableOfDoom.clear(); //This line prints a null pointer exception, because the field hasn't been intialized yet.
        }
    }

7 个答案:

答案 0 :(得分:7)

这就是为什么他们建议你不要在构造函数中调用非final方法。 private方法也是安全的,因为它们无法覆盖。

假设您无法遵循该声音建议,出于某种原因,以下是其他一些可能有帮助的观察结果:

  • 我假设您为了示例而进行了简化,但是对.clear()的调用并没有真正完成示例中的任何内容;你或许可以删除它。
  • 很难说没有看到实际的程序,但你可以通过为变量分配一个新的HashSet而不是调用.clear()来滑冰。

答案 1 :(得分:7)

问题在于:您的执行流程如下所示:

main
->ftw()
  ->wtf()
    ->wtf.dontPanic()

但是由于方法调用是在运行时用Java确定的,ftw.dontPanic()是真正被调用的方法。并且在构造函数完成之前不会设置variableOfDoom,但由于异常,构造函数永远不会完成。

解决方案不是在构造函数中起作用(至少不使用非privatefinal方法)。

答案 2 :(得分:2)

这个例子的结论是:超类初始化不能依赖于完全初始化的子类。

如果ftw没有扩展wtf,那么可以假设在调用构造函数之前完成字段定义中指定的任何初始化。但是,由于ftw扩展了wtf,因此必须先完全初始化wtf,然后才能在ftw中进行任何初始化。由于部分wtf初始化依赖于已初始化的子类中的variableOfDoom,因此您将获得空指针异常。

除此之外的唯一方法是将你的调用与构造函数之外的dontPanic分开。

答案 3 :(得分:1)

您可能会注意到不会阻止私有方法调用覆盖;所以即便如此,在调用任何被构造函数覆盖的东西时要谨慎一些。在其他语言中,建议是避免从构造函数中调用任何虚方法,因为在调用方法时可能无法完全构造对象。

另一种考虑因素是两阶段建设。 http://www.artima.com/forums/flat.jsp?forum=106&thread=106524

.NET Framework设计指南书中有一个名为“create-set-call”的相关习惯用法。创建对象,设置一些属性,调用一些方法。我已经看到这种用法有时被批评为比简单的构造调用更难被发现(即,只是创建和使用)。

答案 4 :(得分:1)

首先,不要这样做。

其次,如果您必须这样做,请执行以下操作:

class wtf {

    wtf(){
            this.dontPanic();
    }

    void dontPanic() {
            System.out.println("super don't panic");
    }
}

class ftw extends wtf {
    HashSet _variableOfDoom; // underscore to remind you not to access it directly

    ftw(){
            super();
    }

    private HashSet getVariableOfDoom()
    {
        if (_variableOfDoom == null) _variableOfDoom = new HashSet();
        return _variableOfDoom;
    }

    void dontPanic() {
            super.dontPanic();
            System.out.println("sub don't panic");
            getVariableOfDoom().clear(); // FOR GREAT JUSTICE!
    }
}

请注意,您不能将variableOfDoom设为最终版。

答案 5 :(得分:0)

不要在构造函数中调用非final方法 - 并非所有实例变量都可以初始化 - 您的代码是一个典型的例子。

如果不知道你真正要完成什么,很难建议“解决问题的最佳方法”,但这里有一个:

class TestRunner {
    public static void main(String[] args) {
            ftw ftw = new ftw();
            ftw.dontPanic();
    }
}

class wtf {

    wtf(){
            wtfDontPanic();
    }

    private final void wtfDontPanic() {
            System.out.println("super don't panic");
    }

    void dontPanic() {
            wtfDontPanic();
    }
}

class ftw extends wtf {
    final HashSet variableOfDoom = new HashSet();

    ftw(){
            super();
            ftwDontPanic();
    }

    private final ftwDontPanic() {
            System.out.println("sub don't panic");
            variableOfDoom.clear(); //This line prints a null pointer exception, because the field hasn't been intialized yet.
    }

    private void dontPanic() {
            super.DontPanic();
            ftwDontPanic();
    }
}

答案 6 :(得分:0)

快速&肮脏的解决方案(不是很好,我知道)

...
System.out.println("sub don't panic");
// must test if object fully initialized, method called in constructor
if (variableOfDoom != null) {
    variableOfDoom.clear();
}