NullPointerException | enum构造函数中的`this`导致NPE

时间:2016-07-18 10:03:03

标签: java enums constructor nullpointerexception java-8

public class Test {
    public static void main(String[] args) {
        Platform1 p1=Platform1.FACEBOOK; //giving NullPointerException.
        Platform2 p2=Platform2.FACEBOOK; //NO NPE why?
    }
}
enum Platform1{
    FACEBOOK,YOUTUBE,INSTAGRAM;
    Platform1(){
        initialize(this);
    };
    public void initialize(Platform1 platform){
        switch (platform) {
        //platform is not constructed yet,so getting `NPE`.
        //ie. we doing something like -> switch (null) causing NPE.Fine!
        case FACEBOOK:
            System.out.println("THIS IS FACEBOOK");
            break;
        default:
            break;
        }
    }
}
enum Platform2{
    FACEBOOK("fb"),YOUTUBE("yt"),INSTAGRAM("ig");
    private String displayName;
    Platform2(String displayName){
        this.displayName=displayName;
        initialize(this);
    };  
    public void initialize(Platform2 platform){
        switch (platform.displayName) {
        //platform not constructed,even No `NPE` & able to access its properties.
        //switch (null.displayName) -> No Exception Why?
        case "fb":
            System.out.println("THIS IS FACEBOOK");
            break;
        default:
            break;
        }
    }
}

任何人都可以解释为什么NullPointerException中有Platform1Platform2中没有preparedStatement。在第二种情况下,我们如何能够访问枚举对象及其属性,甚至在构造对象之前?

6 个答案:

答案 0 :(得分:11)

完全。正如@PeterS在正确构造之前提到的那样使用枚举导致NPE,因为在未构造的枚举上调用了values()方法。

还有一点,我想在此处添加Platform1Platform2两者都尝试在switch()中使用未构造的枚举,但NPE仅在Platform1中。这背后的原因如下: -

 public void initialize(Platform1 platform){
        switch (platform) {

来自Platform1枚举的上面一段代码在交换机中使用platform枚举对象,其中使用内部$SwitchMap$Platform1[]数组并初始化此数组values()方法,因此你得到NPE。但是在Platform2中,switch (platform.displayName)displayName上的比较,它已经初始化并且发生了字符串比较,因此没有NPE。

以下是反编译代码的片段: -

PLATFORM1

 static final int $SwitchMap$Platform1[] =
            new int[Platform1.values().length];

PLATFORM2

switch ((str = platform.displayName).hashCode())
    {
    case 3260: 
      if (str.equals("fb")) {

答案 1 :(得分:3)

在正确构建枚举之前尝试处理枚举时,不能这样做。 (如在完整的东西中构建)。您会注意到错误正在尝试引用枚举的值部分:

Caused by: java.lang.NullPointerException
    at Platform1.values

在处理对象之前,您需要允许对象在内部进行正确的初始化。这将有效:

public static void main(String[] args) {
    Platform1 p1=Platform1.FACEBOOK;
    p1.initialize(p1);
    //Platform1.YOUTUBE giving NullPointerException why?
    Platform2 p2=Platform2.FACEBOOK;
    //NO NPE
}

enum Platform1{
    FACEBOOK,YOUTUBE,INSTAGRAM;
    Platform1(){
        //initialize(this);
    };

显然,您的初始化函数应该重命名,因为它只是报告值。 您的第二个示例提供了值,因此可以正常工作。

来自其中一个Java文档:

  

枚举声明定义了一个类(称为枚举类型)。枚举   类体可以包括方法和其他字段。编译器   在创建枚举时自动添加一些特殊方法。对于   例如,它们有一个返回数组的静态值方法   按顺序包含枚举的所有值   声明。这种方法通常与之结合使用   for-each构造迭代枚举类型的值。对于   例如,下面的Planet类示例中的代码将迭代   太阳系中的所有行星。

答案 2 :(得分:3)

正如已经指出的那样,枚举上的switch在内部调用values方法,但只有在初始化所有枚举常量后才会初始化:

Caused by: java.lang.NullPointerException
    at Platform1.values(Test.java:17)
    at Platform1$1.<clinit>(Test.java:25)
    ... 4 more

Platform2中,这不会发生,因为字符串中的switch

更面向对象的方法是创建一个initialize方法,构造函数调用该方法并被需要专门初始化的常量覆盖:

enum Platform3 {
    FACEBOOK {
        @Override
        protected void initialize() {
            System.out.println("THIS IS FACEBOOK");
        }
    },
    YOUTUBE,
    INSTAGRAM;

    Platform3() {
        initialize();
    }

    // this acts as the default branch in the switch
    protected void initialize() {
        System.out.println("THIS IS OTHER PLATFORM: " + this.name());
    }
}

答案 3 :(得分:1)

您正在获取NPE,因为您正在引用尚未构建的实例。 Platform1.FACEBOOK nullPlatform1,直到构造FACEBOOK实例的Platform1构造函数完成。

initialize构造函数调用switch,其中包含caseswitch中的Platform1.FACEBOOK读取FACEBOOK。由于FACEBOOK的构造函数尚未返回,因此null引用为空。 Java Language Specification不允许case作为switch中的 #include <fstream> #include <locale> #include <codecvt> const locale utf8_locale = locale(locale(), new codecvt_utf8<wchar_t>()); wofstream file(url); file.imbue(utf8_locale); file << L"իմբյու" << endl; ,它会像您找到的那样抛出运行时异常。

答案 4 :(得分:1)

以下示例显示了初始化生命周期:

public class Test {
    //                                v--- assign to `PHASE` after creation
    static final Serializable PHASE = new Serializable() {{

        //                               v---it is in building and doesn't ready...
        System.out.println("building:" + PHASE); //NULL

        System.out.println("created:" + this);//NOT NULL
    }};


    public static void main(String[] args) {
        //                            v--- `PHASE` is ready for use
        System.out.println("ready:" + PHASE); //NOT NULL
    }
}

简而言之,枚举常量在构建过程中未初始化。换句话说,当前的枚举实例将被分配给关联的常量,直到整个建筑工作完成。

switch语句将调用values()方法,但枚举常量处于构建状态并且尚未准备好使用。为了避免客户端代码更改其内部$VALUES数组,values()克隆其内部数组,因为枚举常量尚未准备就绪NullPointerException 1}}被抛出。这是字节码values()方法&amp;静态初始化块:

static {};
    10: putstatic     #14           // Field FACEBOOK:LPlatform1;
    23: putstatic     #16           // Field YOUTUBE:LPlatform1;
    //  putstatic  //Other Fields

    61: putstatic     #1            // Field $VALUES:[LPlatform1;
    // `$VALUES` field is initialized at last ---^
public static Platform1[] values();

    // v--- return null
    0: getstatic     #1 // Field $VALUES:[LPlatform1;

    // v--- null.clone() throws NullPointerException
    3: invokevirtual #2 // Method "[LPlatform1;".clone:()Ljava/lang/Object;

答案 5 :(得分:1)

简答: 调用初始化方法的地方,当枚举类被类加载器(正在进行)加载时被调用,因此您无法访问类级属性,即静态。您可以在哪里访问非staic属性。

<强> 1。当你第一次在代码中引用Enum时,会调用enum的构造函数。

Platform1 p1=Platform1.FACEBOOK;

此行将使用类加载器加载Enum Platform1的类。并且将为该枚举中的每个条目/实例调用构造函数,此处为3。

下面的代码将打印三个哈希码。

   enum Platform1{
    FACEBOOK,YOUTUBE,INSTAGRAM;
    Platform1() {
      initialize(this);
    };
    public void initialize(Platform1 platform){
      System.out.println(platform.hashCode()); // it will print three hash codes
      switch (platform.hashCode()) {
        case 1:
          System.out.println(platform);
          break;
        default:
          break;
      }
    }
  }

因此,简而言之,当调用此初始化方法时,Enum类未完全加载且正在进行中。因此,您无法在该时间点访问该枚举的任何静态属性或方法。

<强> 2。当您使用下面的行时,它会调用values()一个静态方法

 public void initialize(Platform1 platform){
      switch (platform) {
      }
    }

只需将静态方法更改为某种等效的非静态方法即可。等,

  enum Platform1{
    FACEBOOK,YOUTUBE,INSTAGRAM;
    Platform1() {
      initialize(this);
    };
    public void initialize(Platform1 platform){
      System.out.println(platform.hashCode());
      switch (platform.toString()) { // toString() is non static method
        case "FACEBOOK":
          System.out.println(platform);
          break;
        default:
          break;
      }
    }
  }

第3。因此,您的问题的答案是,当Enum类初始化时,您

  • 无法访问任何静态内容

  • 但可以访问非静态内容

因此,以下代码正在为您工作

enum Platform2{
    FACEBOOK("fb"),YOUTUBE("yt"),INSTAGRAM("ig");
    private String displayName;
    Platform2(String displayName){
        this.displayName=displayName;
        initialize(this);
    };  
    public void initialize(Platform2 platform){
        switch (platform.displayName) {
        case "fb":
            System.out.println("THIS IS FACEBOOK");
            break;
        default:
            break;
        }
    }
}

<强> 4。如果你将displayName更改为static,那么事情就会中断。

  enum Platform2{
    FACEBOOK("fb"),YOUTUBE("yt"),INSTAGRAM("ig");
    private static String displayName = "FACEBOOK";
    Platform2(String displayName){
      initialize(this);
    };
    public void initialize(Platform2 platform){
      switch (platform.displayName) {
        case "FACEBOOK":
          System.out.println(platform);
          break;
        default:
          break;
      }
    }
  }