带静态变量的NullPointerException

时间:2013-11-08 09:51:35

标签: java nullpointerexception static-variables

我只是非常奇怪(对我来说)java的行为。我有以下课程:

public abstract class Unit {
    public static final Unit KM = KMUnit.INSTANCE;
    public static final Unit METERS = MeterUnit.INSTANCE;
    protected Unit() {
    }
    public abstract double getValueInUnit(double value, Unit unit);
    protected abstract double getValueInMeters(double value);
}

public class KMUnit extends Unit {
    public static final Unit INSTANCE = new KMUnit();

    private KMUnit() {
    }
//here are abstract methods overriden
}

public class MeterUnit extends Unit {
    public static final Unit INSTANCE = new MeterUnit();

    private MeterUnit() {
    }

///abstract methods overriden
}

我的测试用例:

public class TestMetricUnits extends TestCase {

    @Test
    public void testConversion() {
        System.out.println("Unit.METERS: " + Unit.METERS);
    System.out.println("Unit.KM: " + Unit.KM);
    double meters = Unit.KM.getValueInUnit(102.11, Unit.METERS);
    assertEquals(0.10211, meters, 0.00001);
    }
}
  1. MKUnit和MeterUnit都是静态初始化的单例,所以 在课堂加载期间。构造者是私人的,所以他们不可能 在其他地方初始化。
  2. 单元类包含对MKUnit.INSTANCE和的静态最终引用 MeterUnit.INSTANCE
  3. 我希望如此:

    • 加载KMUnit类并创建实例。
    • 加载MeterUnit类并创建实例。
    • 已加载单元类,并且KM和METERS变量都已初始化,它们是最终的,因此无法更改。

    但是当我在maven的控制台中运行我的测试用例时,我的结果是:

     T E S T S
    
    Running de.audi.echargingstations.tests.TestMetricUnits<br/>
    Unit.METERS: m<br/>
    Unit.KM: null<br/>
    Tests run: 3, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.089 sec <<< FAILURE! - in de.audi.echargingstations.tests.TestMetricUnits<br/>
    testConversion(de.audi.echargingstations.tests.TestMetricUnits)  Time elapsed: 0.011 sec  <<< ERROR!<br/>
    java.lang.NullPointerException: null<br/>
            at <br/>de.audi.echargingstations.tests.TestMetricUnits.testConversion(TestMetricUnits.java:29)
    <br/>
    
    Results :
    
    Tests in error:
      TestMetricUnits.testConversion:29 NullPointer
    

    有趣的是,当我通过JUnit跑步者从eclipse运行此测试时,一切都很好,我没有NullPointerException并且在控制台中我有:

    Unit.METERS: m
    Unit.KM: km
    

    所以问题是:单位中的KM变量为空的原因是什么(同时METERS不为空)

2 个答案:

答案 0 :(得分:4)

静态初始化可能很棘手。你有一个A - &gt;之间的相互依赖关系。 B和B - &gt; A.这个坏主意的原因是因为JVM如何从类中自上而下开始加载静态 - 如果它遇到一个尚未初始化的新类,它会等到它初始化该类及其依赖项,递归地,直到一切都准备好,然后继续。

除非它已经加载了一个类。如果A指向B,而B指的是A,则它不能再次开始加载A,或者它将是无限循环(因为A将再次加载B,其加载A)。因此,在这种情况下,它基本上说“已经开始加载,没有更多事要做,继续”。

故事的道德:根据加载类的顺序,当您点击此行时,可能无法初始化KMUnit.INSTANCE:

public static final Unit KM = KMUnit.INSTANCE;

想象一下,您是JVM,并开始加载KMUnit。它必须在第一次看到它时加载Unit,以便例如当我们到达第一个创建时(或者可能之前 - 我在JVM静态加载时触摸模糊)创建一个对象的单元。但这反过来会触发Unit中静态的初始化,包括:

public static final Unit KM = KMUnit.INSTANCE;
public static final Unit METERS = MeterUnit.INSTANCE;

确定。现在Unit完成加载,我们完成了为KMUnit.INSTANCE构建KMUnit ...但是等待 - 我们已经设置了KM = KMUnit.INSTANCE,当时为空。所以它仍然是空的。糟糕。

另一方面,如果Unit首先加载,则在初始化之前等待KMUnit加载,因此在我们实际运行初始化程序时设置KMUnit.INSTANCE。

我想。我有点睡眠不足,而且我不是课堂上的专家。

答案 1 :(得分:0)

  

我希望如此:

- KMUnit class is loaded and instance is created.
- MeterUnit class is loaded and instance is created.
- Unit class is loaded and both KM and METERS variable are initialized, they are final so they cant be changed.

即使没有进入语言规范,也很容易理解为什么上述顺序是不可能的。

KMUnit延伸Unit。要创建静态字段KMUnit.INSTANCE,必须创建其类KMUnit。要创建KMUnit,必须创建其超类Unit。最后要创建课程Unit,必须为其分配静态字段KMMETERS

但是我们通过尝试创建类KMUnit来到这里,我们还没有加载类Meters。因此,不可能为超类的静态字段分配正确的值(即对完全构造的对象的引用)。

您描述的步骤有两个问题:

您描述的步骤中的错误是您要延迟Unit的加载,这是无法完成的。

希望这会有所帮助。静态初始化器不容易理解,它们的规格更不容易理解。或许更容易理解为什么不能做某些事情,因此我的非正式答案。