我只是碰到了非常奇怪的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);
    }
}
  • MKUnit和MeterUnit都是静态初始化的单例,因此
    在上课时。构造函数是私有的,因此不能
    在其他任何地方初始化。
  • Unit类包含对MKUnit.INSTANCE和
    MeterUnit.INSTANCE

  • 我希望:

    加载
  • 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
    

    有趣的是,当我通过JUnitRunner从Eclipse运行此测试时,一切都很好,我没有NullPointerException,在控制台中,我有:
    Unit.METERS: m
    Unit.KM: km
    

    所以问题是:Unit中的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,以便例如创建一个对象,该对象是我们第一次创建时(或之前)的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。

    我认为。我有点睡眠不足,而且我不是类加载方面的专家。

    07-27 20:09