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

java enums constructor nullpointerexception java-8

1446 просмотра

6 ответа

0 Репутация автора

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 и его свойствам даже до того, как объект будет построен?

Автор: user4768611 Источник Размещён: 18.07.2016 10:03

Ответы (6)


3 плюса

1762 Репутация автора

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

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 определяет класс (называемый тип enum). Тело класса enum может включать в себя методы и другие поля. Компилятор автоматически добавляет некоторые специальные методы при создании перечисления. Например, у них есть метод статических значений, который возвращает массив, содержащий все значения перечисления в порядке их объявления. Этот метод обычно используется в сочетании с конструкцией for-each для итерации по значениям типа enum. Например, этот код из приведенного ниже примера класса «Планета» повторяет все планеты в Солнечной системе.

Автор: PeterS Размещён: 18.07.2016 11:02

11 плюса

404 Репутация автора

Именно так. Подобно тому, как @PeterS упоминал использование enum до того, как он был правильно сконструирован, это вызывает NPE, потому что метод values ​​() вызывается для не сконструированного enum.

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

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

Выше кусок кода из Platform1enum использует platformобъект enum в switch, где используется внутренний $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")) {
Автор: Neha Vari Размещён: 18.07.2016 12:31

1 плюс

3678 Репутация автора

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

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

Автор: Boris B. Размещён: 18.07.2016 12:47

3 плюса

3791 Репутация автора

Как уже указывалось, switchon-перечисления внутренне вызывают 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());
    }
}
Автор: Helder Pereira Размещён: 14.08.2017 10:51

1 плюс

19392 Репутация автора

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

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 не была инициализирована во время самого построения. Другими словами, текущий экземпляр enum будет назначен связанной константе до тех пор, пока не будут завершены все строительные работы.

Оператор switch будет вызывать values()метод, однако константы enum находятся в сборке и не готовы к использованию. Чтобы клиентский код не изменил свой внутренний $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;
Автор: holi-java Размещён: 17.08.2017 02:27

1 плюс

911 Репутация автора

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

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;
      }
    }
  }
Автор: JTeam Размещён: 17.08.2017 09:42
Вопросы из категории :
32x32