簡體   English   中英

當嵌套的Enum在其構造函數中引用父靜態成員時,為什么會得到NPE?

[英]Why do I get an NPE when a nested Enum references a parent static member in its constructor?

重建的條件(據我所知):

  1. 嵌套枚舉引用父靜態成員
  2. 嵌套類
  3. 父類的靜態成員將枚舉作為嵌套類的構造函數參數
  4. 枚舉在父類中的任何其他內容之前由外部類引用

在線運行此代碼: https//repl.it/repls/PlushWorthlessNetworking

import java.util.ArrayList;

class Recreate {

  private static ArrayList FEATURES = new ArrayList();

  public enum Car {
    TESLA(FEATURES);
    Car(ArrayList l) { }
  }

  public static class Garage {
    final Car car;

    Garage(Car car) {
      this.car = car;
    }
  }

  public static Garage ONE_CAR_GARAGE = new Garage(Car.TESLA);
}

class Main {
  public static void main(String[] args) {
    // inclusion of this line causes the next line to NPE
    System.out.println(Recreate.Car.TESLA);

    System.out.println(Recreate.ONE_CAR_GARAGE.car.toString());
  }
}

以下是發生的事情:

  1. main方法開始執行
  2. 您可以參考Recreate.Car.TESLA
  3. 類加載器開始加載和初始化enum Car 如下所述,尚未加載或初始化類Recreate
  4. TESLA的初始化程序指的是FEATURES
  5. 這會導致加載和初始化類Recreate
  6. 由於靜態初始化的一部分Recreate ,類Garage被加載,intialized,並且實例ONE_CAR_GARAGE被創建。

這里的問題是,此時, enum Car的構造不完整, Car.TESLA的值為null

盡管類可以嵌套,但嵌套類不是作為外部類初始化的一部分加載和初始化的情況。 它們可能看起來嵌套在源中,但每個類都是獨立的。 靜態嵌套類等同於頂級類。 非靜態類也是相同的,但是能夠通過隱藏引用引用包含類中的成員。

您可以自己查看是否在調試器中運行此命令,將斷點放在多個位置,並檢查每個斷點處的堆棧。

我使用以下代碼在Eclipse中對此進行了測試/調試,並在指定的位置設置了斷點。 它與您的代碼略有不同,但不應表現得不同:

public class Foo5
{
    static class Recreate {

        private static ArrayList FEATURES = new ArrayList();

        public  enum Car {
          TESLA(FEATURES);
          Car(ArrayList l) { 
              System.out.println("car"); // *** Breakpoint ***
          }
        }
        public static Garage ONE_CAR_GARAGE = new Garage(Car.TESLA);

        public static class Garage {
            final Car car;

            Garage(Car car) {
              this.car = car;  // *** Breakpoint ***
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Recreate.Car car = Recreate.Car.TESLA;
        System.out.println(Recreate.Car.TESLA);
        System.out.println(Recreate.ONE_CAR_GARAGE.car.toString());
    }   
}

您將要擊中的第一個斷點將是Garage(Car car)構造函數中的斷點。 在那一點上檢查堆棧你會看到

Foo5$Recreate$Garage.<init>(Foo5$Recreate$Car) line: 23 
Foo5$Recreate.<clinit>() line: 17   
Foo5$Recreate$Car.<clinit>() line: 12   
Foo5.main(String[]) line: 29    

因此,當調用Garage構造函數時,它尚未從創建Car返回。 這是由您在類之間創建的復雜依賴關系決定的,因此解決方案是解開依賴關系。 你如何做到這將取決於你的最終目標。

你有一個隱藏的循環依賴,令JVM感到困惑。 我們來看看你的代碼吧。

class Recreate {

  private static ArrayList FEATURES = new ArrayList();

  public enum Car {
    TESLA(FEATURES);
    Car(ArrayList l) { }
  }

  public static class Garage {
    final Car car;

    Garage(Car car) {
      this.car = car;
    }
  }

  public static Garage ONE_CAR_GARAGE = new Garage(Car.TESLA);
}

我們還需要一些來自JLS頁面的片段。

類或接口類型T將在第一次出現以下任何一個之前立即初始化:

  • 使用由T聲明的靜態字段,該字段不是常量變量(第4.12.4節)。

12.4.2。 詳細的初始化程序

...

  1. 接下來,按文本順序執行類的類變量初始值設定項和類的靜態初始值設定項,或接口的字段初始值設定項,就好像它們是單個塊一樣。

因此,我們的靜態數據在首次引用時正在初始化。 現在,你的Car.TESLA是隱式static final ,但根據定義 ,它不是常數。

常量變量是基本類型或類型String的最終變量,使用常量表達式初始化

所以對於我們來說,也有在這里打球三個靜態非恆定的變量: FEATURESTESLAONE_CAR_GARAGE

現在,在您的工作案例中,您引用Recreate.ONE_CAR_GARAGE 這是一個靜態字段的引用Recreate ,所以FEATURES ,然后ONE_CAR_GARAGE得到初始化。 然后,初始化過程中 ONE_CAR_GARAGETESLA被因為它的枚舉類中引用初始化。 一切都很好。

但是,如果我們過早地引用枚舉,那么我們會以錯誤的順序執行操作。 Recreate.Car.TESLA被引用,因此TESLA被初始化。 TESLA引用FEATURES ,所以Recreate已被初始化。 這將導致FEATURESONE_CAR_GARAGE 之前得到初始化TESLA完成現有的。

這是隱藏的依賴性,讓你絆倒。 Recreate.Car取決於Recreate取決於Recreate.Car ONE_CAR_GARAGE字段移動到Garage類將導致它無法使用FEATURES初始化並將解決您的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM