带静态变量的NullPointerException

我只是打了很奇怪(对我来说)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. Unit类包含对MKUnit.INSTANCE和MeterUnit.INSTANCE的静态最终引用

我希望如此:

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

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

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

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

 Unit.METERS: m Unit.KM: km 

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

静态初始化可能很棘手。 你有一个A – > B和B之间的相互依赖 – > 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 ,当时为null。 所以它仍然是空的。 哎呀。

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

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

我希望如此:

 - 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 ,这是无法完成的。

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