8

我刚刚遇到了非常奇怪的(对我而言)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 在控制台中运行我的测试用例时,我的结果是:

 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 runner 从 eclipse 运行这个测试时,一切都很好,我没有NullPointerException,在控制台中我有:

Unit.METERS: m
Unit.KM: km

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

4

2 回答 2

5

静态初始化可能很棘手。您在 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。

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

于 2013-11-11T23:34:49.413 回答
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,这是无法完成的。

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

于 2013-11-11T23:58:49.843 回答