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

4
假设我有两个以下定义的类:

Class A:

class A {
    public:
        virtual void foo() const;
};

Class B:

class B : public A {
    public:
        void foo() const override;
};

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方法中可能发生空指针异常呢?这个检查不难,有时非常有用。


B.foo()将只打印“null”; 如果你真的想强制执行,可以使用System.out.println(bar.length()); - Andrei Fierbinteanu
“NullPointer”指的是它将打印“null”,而不是您期望的内容。编译器既不会警告您打印空值,也不会警告您调用子类方法。为什么要这样做呢? - Paul Jowett
嘿,我可真是个伟大的程序员,给人们提供如何强制出现NPE错误的建议。 - Andrei Fierbinteanu
是的,我知道它打印出了null。但是我认为此时bar是一个null指针。 - anon
你的IDE能够捕捉到未初始化变量的使用将会很好,但这可能太微妙了。如果你认为你有一种方法来标记这些,请跳转到Eclipse项目! - Alex Feinman
编译器怎么知道在你的情况下null是不可取的?也许你想让它为null。 - Steve Kuo
6个回答

8

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

以下是来自《Effective Java 2nd Edition, Item 17: Design and document for inheritance, or else prohibit it》的一些引用:

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

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

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

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


FindBugs甚至会警告bar是一个原始值,例如intlong,我认为这是错误的。毕竟,原始值有有意义的默认值(例如0L)。 - Marcel Stör

4

3
出现问题的原因是:你在A的构造函数中调用了foo方法,但在你调用它的时候,对象还没有完全构造好。B的构造函数还没有执行,所以bar仍然具有默认值null。这说明从构造函数中调用非final实例方法是一个不好的想法。
Java编译器不会对此发出警告 - 它会对某些结构发出警告,但编译器的第一个目的是编译您的代码,它并不是一个非常复杂的代码分析工具。
您可以使用静态代码分析工具,例如FindBugsPMD来查找代码中的此类问题。

2

在他的著作《Effective Java》中,父亲Josh Bloch说:

在Java中,不得从构造函数调用虚拟方法。

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


+1,我喜欢“在Java中不要从构造函数调用虚拟方法”的规则。 - bakkal

0
一般来说,编译器不会尝试证明字段的空值。它会对局部变量进行此操作,但有太多方法可以更改字段的值(例如序列化、反射或其他技巧),因此它无法彻底完成。 如果可能的话,您应该尝试声明字段为final,这对于此和许多其他事情都有很大帮助。或者,编写代码以防止出现null(例如使用"Test".equals(foo)而不是foo.equals("Test")或显式的null检查)。

0

编译器的工作不是检查所有可能或可能不是编程错误的“不太难检查”的东西(而这样的东西有数百个)。

可以说,IDE 可能会用可配置的警告标记此类问题,但同样存在太多类似的问题,如果 IDE 在按需编译期间必须全部检查它们,那么日常工作将会变得过于缓慢。

这确实是静态样式检查器(如 FindBugs)的工作。而且 - 惊喜! - 它对于这种情况有一个检查:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接