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 クラスが読み込まれ、インスタンスが作成されます。
  • Unit クラスがロードされ、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

問題は、Unit の KM 変数が nullである (同時に METERS が null ではない)理由は何であるかということです。

4

2 に答える 2

5

静的初期化は扱いにくい場合があります。A -> B と B -> A の間に相互依存性があります。これが悪い考えである理由は、JVM がクラス内でトップダウンで静的をロードし始める方法のためです。新しいクラスにヒットした場合、まだ初期化されていません。 、すべての準備が整うまで、そのクラスとその依存関係を再帰的に初期化するまで待機し、次に進みます。

すでにクラスをロードしている場合を除きます。A が B を参照し、B が A を参照している場合、A のロードを 2 度目に開始することはできません。または、無限ループになります (A が B を再度ロードし、A が A をロードするため)。したがって、その場合、基本的には「すでにロードを開始しています。これ以上何もする必要はありません。続行します」と表示されます。

教訓: クラスのロードの順序によっては、次の行にヒットしたときに KMUnit.INSTANCE が初期化されない場合があります。

public static final Unit KM = KMUnit.INSTANCE;

あなたが JVM であり、KMUnit のロードを開始すると想像してください。たとえば、最初の作成に到達したときに Unit のサブクラスであるオブジェクトを作成するために、Unit を初めて見たときにロードする必要があります (または、おそらくその前に - 私は JVM の静的ロードについて少し霧がかかっています)。 )。しかし、それは次に、これを含む Unit の statics の初期化をトリガーします:

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

Ok。これで、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ます。static フィールドを作成するには、KMUnit.INSTANCEそのクラスを作成する必要KMUnitがあります。KMUnitそのスーパークラスを作成するには、作成Unitする必要があります。最後に、クラスUnitをその静的フィールドKMに作成し、METERS割り当てる必要があります。

しかし、クラスを作成しようとしてここにたどり着きましたが、まだKMUnitクラスをロードしてMetersいません。そのため、スーパー クラスの静的フィールドに正しい値 (つまり、完全に構築されたオブジェクトへの参照) を割り当てることは不可能です。

あなたが説明する手順には2つの問題があります。

説明した手順のエラーは、の読み込みを遅らせていることですUnit。これは実行できません。

お役に立てれば。静的イニシャライザは理解するのが容易ではなく、その仕様はさらに理解しにくいものです。なぜ何かができないのかを合理化する方がおそらく簡単なので、私の非公式の答えです。

于 2013-11-11T23:58:49.843 に答える