为什么我没有收到这个问题的警告?

时间:2010-06-04 07:53:58

标签: java ide syntax

假设我按以下方式定义了两个类:

public class A {

   public A() {
      foo();
   }

   public void foo() {
      System.out.println("A");
   }
}

public class B extends A {

   private String bar;

   public B() {
      bar = "bar";
   }

   @Override
   public void foo() {
      System.out.println(bar);
   }

}

然后我以下列方式实例化B:

A test = new B();

那么为什么编译器分别不能让IDE警告我在B的foo方法中会有一个NullPointer?这不是很难检查,有时非常有用。

6 个答案:

答案 0 :(得分:7)

虽然这是设计错误,但它不是语法错误。

以下是来自 Effective Java 2nd Edition,第17项:设计和继承文档的引用,或者禁止它

  

为了允许继承,类必须遵守一些限制。 构造函数不得直接或间接调用可覆盖的方法。如果违反此规则,将导致程序失败。超类构造函数在子类构造函数之前运行,因此在子类构造函数运行之前将调用子类中的重写方法。如果重写方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。

一个顺从的编译器会让它编译得很好,因为它在语言上是合法的。幸运的是,代码分析工具可用于查找这些设计错误,例如: findbugs

  

UR:从超类构造函数调用的字段方法的未初始化读取(UR_UNINIT_READ_CALLED_FROM_SUPER_CONSTRUCTOR)

     

在超类的构造函数中调用此方法。此时,该类的字段尚未初始化。

答案 1 :(得分:4)

这是一个经典的问题。不要在构造函数中使用实例方法。

你可能想看看PMD,尤其是。 “ConstructorCallsOverridableMethod”规则。

http://pmd.sourceforge.net/rules/design.html

答案 2 :(得分:3)

出现的问题是:您在foo的构造函数中调用A方法,但是在您调用它时,该对象尚未完全构造。 B的构造函数尚未执行,因此bar仍具有默认值null。这说明了为什么从构造函数中调用非最终实例方法是个坏主意。

Java编译器没有对此发出警告 - 它警告某些构造,但编译器的第一个目的是编译代码,它并不是一个非常复杂的代码分析工具。

您可以使用静态代码分析工具(如FindBugsPMD)在代码中查找此类问题。

答案 3 :(得分:2)

父亲Josh Bloch在他的着作“有效Java”中说道。

您不能从Java中的构造函数中调用虚方法。

这可能导致您正在观察的确切问题。在致电B.foo()时,B尚未初始化,因此B.barnull

答案 4 :(得分:0)

通常,编译器不会尝试证明字段的空值。它将为局部变量执行此操作,但是有太多方法可以更改字段的值(即序列化,反射或其他技巧),以便它可以完成一项彻底的工作。

如果可能的话,你应该尝试声明最终的字段,这对这个和许多其他事情有很大的帮助。或者,编写代码来防御空值(即“Test”.equals(foo)而不是foo.equals(“Test”)或显式空值检查)

答案 5 :(得分:0)

编译器的工作不是检查各种“不太难检查”的东西,这可能是也可能不是编程错误(并且有数百种)。

可以说,IDE可能会使用可配置的警告来标记这一点,但同样,有太多这样的东西,如果IDE必须在按需编译期间检查所有内容,它会使日常工作变得太慢。

这就是像FindBugs这样的静态样式检查器的用途。而且 - 惊喜! - 它检查了这种情况:

UR: Uninitialized read of field method called from constructor of superclass