2009-06-08 57 views
2

任何人都可以用自己对解决以下问题的最佳途径意见有何评论?我已经尝试过init块并在构造函数中初始化该字段,但在重写的方法试图清除它之前,都不会调用它。NullPointerException异常与覆盖方法

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

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

假设你不能遵循合理的建议,由于某些原因,这里有一些其他的意见,可能会帮助:

  • 我假设你简单的例子来说,但电话到.clear()在这个例子中并没有真正完成任何事情;你可能会删除它。
  • 很难说总没有看到实际的程序,但是你可以通过给变量分配一个新的HashSet而不是调用.clear()来轻松。
+0

这是正确的,明确只是抛出异常的一个例子。 – danieljimenez 2009-06-08 18:17:31

+0

我接受这个答案,因为我不知道这是推荐的做法。我已经在IntelliJ中启用了检查,以防止我们在将来做这件事。谢谢汉克! – danieljimenez 2009-06-08 18:24:48

+0

你不能在构造函数之外的任何地方(在这种情况下是dontPanic()方法)指定一个最终变量(在这种情况下是variableOfDoom)一个新的HashSet。这是你的第二点所暗指的吗? – neesh 2009-06-08 18:29:42

7

这里的问题:你的执行流程是这样的:

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

但由于方法调用在Java中在运行时确定,ftw.dontPanic()是真的被调用的方法。而variableOfDoom不会被置位,直到完成构造,但由于异常的构造函数将永远不会完成。

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

+0

+1在30秒内比我快。 – 2009-06-08 18:16:27

+0

是的,我意识到问题和执行流程,我只是寻求最佳实践的建议,以避免这种情况。 – danieljimenez 2009-06-08 18:18:21

+0

那就是“不要在构造函数中调用非私有的非最终方法”位。 – 2009-06-08 18:23:52

2

从这个例子外卖是:超类的初始化不能依赖于子类完全被初始化。

如果ftw没有扩展wtf,那么可以假定在调用构造函数之前在字段定义中指定的任何初始化都会完成。但是,由于ftw扩展了wtf,所以在任何初始化可能发生在ftw之前,wtf必须完全初始化。由于wtf初始化的一部分依赖于子类中的variableOfDoom被初始化,所以你得到一个空指针异常。

出了这唯一的办法是让你的构造函数外你的电话分开,dontPanic。

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(); 
    } 
} 
1

您可能会注意到,都不妨碍私有方法从调用覆盖;所以即使这样,在调用任何被构造函数覆盖的东西时也要小心谨慎。在其他语言中,建议是避免从构造函数调用任何虚方法,因为在调用方法时对象可能不完全构造。

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

有一个名为“打造集呼叫” .NET框架设计指南书相关的成语。创建对象,设置一些属性,调用一些方法。我曾经看到这种用法有时被批评为比简单的构建调用(即创建和使用)更难以发现。

0

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

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

而variableOfDoom仍然可以是最终的。 – 2009-06-11 20:20:12

+0

为什么downvote(6个月后)? – 2009-12-18 09:34:19

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是最终决定。

+0

不知道为什么要创建一个HashSet,以便它可以被清除。我会检查一个空值,并假设它没有被初始化就需要被清除。 – 2009-06-11 20:19:36