Подтвердить что ты не робот

NullPointerException | `this` внутри конструктора перечислений, вызывающего NPE

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 в Platform1, но не в Platform2. Как во втором случае мы можем получить доступ к объекту enum и его свойствам еще до создания объекта?

4b9b3361

Ответ 1

Совершенно верно. Так же, как @PeterS, упомянутый с использованием enum до того, как он был правильно сконструирован, вызывает NPE, потому что метод value() вызывается в незастроенном enum.

Еще один момент, я хотел бы добавить здесь, что Platform1 и Platform2 обе пытаются использовать unconstructed enum в switch(), но NPE находится только в Platform1. Причина этого заключается в следующем: -

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

Над фрагментом кода из Platform1 enum используется platform объект enum в коммутаторе, где используется массив $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")) {

Ответ 2

Вы не можете этого сделать, пытаясь работать с перечислением до того, как оно будет правильно сконфигурировано. (Как в полной конструкции). Вы заметите, что ошибка пытается ссылаться на часть значений перечисления:

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:

Объявление enum определяет класс (называемый типом перечисления). Перечисление класс может включать методы и другие поля. Компилятор автоматически добавляет некоторые специальные методы при создании перечисления. Для Например, у них есть метод статических значений, который возвращает массив содержащий все значения перечисления в том порядке, в котором они находятся объявлен. Этот метод обычно используется в сочетании с для каждой конструкции для итерации по значениям типа перечисления. Для Например, этот код из примера класса Planet ниже итерации все планеты в Солнечной системе.

Ответ 3

Как уже указывалось, switch на enums внутренне вызывает метод values, но это будет инициализироваться только после инициализации всех констант перечисления:

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

В Platform2 это не происходит, потому что строки switch in on.

Более объектно-ориентированный подход заключался бы в создании метода 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());
    }
}

Ответ 4

Вы получаете NPE, потому что ссылаетесь на экземпляр, который еще не создан. Platform1.FACEBOOK null, пока конструктор Platform1, который конструирует экземпляр FACEBOOK, не будет завершен.

Конструктор Platform1 вызывает initialize, который содержит switch. case в этом switch читает Platform1.FACEBOOK. Поскольку конструктор FACEBOOK еще не вернулся, ссылка FACEBOOK имеет значение null. Спецификация Java Language не позволяет null как case в switch, она будет генерировать исключение во время выполнения так же, как вы нашли.

Ответ 5

В приведенном ниже примере показан жизненный цикл инициализации:

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
    }
}

В двух словах константа перечисления не была инициализирована во время самого построения. Другими словами, текущий экземпляр enum будет привязан к связанной константе до тех пор, пока все работы здания не будут завершены.

Оператор switch вызывает метод values(), однако константы перечисления находятся в здании и не готовы к использованию. Чтобы код клиента не изменил свой внутренний массив $VALUES, values() будет клонировать свой внутренний массив, поскольку константы перечисления еще не готовы, а затем был отброшен NullPointerException. здесь используется метод байт-кода values() и статический блок инициализации:

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;

Ответ 6

Короткий ответ: место, где ваш метод инициализации вызова вызывается, когда этот класс enum загружается загрузчиком класса (в процессе), и, следовательно, вы не можете получить доступ к свойствам уровня класса i.e static. Где вы можете получить доступ к нестационарным свойствам.

1. Конструктор 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. Когда вы используете строку ниже, она вызывает значения() статический метод,

 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;
      }
    }
  }