Почему я получаю NPE, когда вложенный Enum ссылается на родительский статический член в его конструкторе?

Вы можете создать свой собственный репозиторий, который расширяет SimpleJpaRepository. Затем реализуем RepositoryFactoryBean и RepositoryFactory, которые будут возвращать репозитории, содержащие вашу реализацию. Для JPA вы можете использовать пример в https://github.com/jkubrynski/spring-data-examples/tree/master/src/main/java/com/kubrynski/data/repository/generic

Помните о том, чтобы включить ваш завод в @EnableJpaRepositories, указав repositoryFactoryBeanClass.

4
задан Silvio Mayolo 13 July 2018 в 05:02
поделиться

2 ответа

Вот что происходит:

  1. Основной метод запускает выполнение
  2. Вы ссылаетесь на Recreate.Car.TESLA
  3. Загрузочный загрузчик классов загружается и инициализировать enum Car. Как отмечено ниже, класс Recreate еще не загружен или не инициализирован.
  4. Инициализатор для TESLA относится к FEATURES
  5. . Это вызывает загрузку класса Recreate и initialized
  6. Как часть статической инициализации Recreate, загружается класс Garage, инициализируется и создается экземпляр 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. Это диктуется запутанными зависимостями, которые вы создали между классами, поэтому решение заключается в распутывании зависимостей. Как вы это сделаете, это будет зависеть от ваших конечных целей.

4
ответ дан Jim Garrison 17 August 2018 в 13:42
поделиться
  • 1
    Хотя это действительно то, что происходит в онлайн-инструменте, но такое поведение указано в JLS? – Jai 13 July 2018 в 04:57
  • 2
    Да, так работает язык. Я отлаживал это в Eclipse. Я добавлю код, который я использовал, и укажу, где я установил точки останова. – Jim Garrison 13 July 2018 в 04:58

У вас есть скрытая циклическая зависимость, которая запутывает 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).

& nbsp;

12.4.2. Подробную процедуру инициализации

...

  1. Затем выполните либо инициализаторы переменных класса, и статические инициализаторы класса, либо инициализаторы полей интерфейса, в текстовом порядке, как если бы они были одним блоком.

Таким образом, наши статические данные инициализируются, когда они впервые упоминаются. Теперь ваш Car.TESLA неявно static final, но он не является константой, согласно определению .

Постоянная переменная является конечной переменной примитивного типа или введите String, которая инициализируется константным выражением

. Итак, для наших целей здесь есть три статических непостоянных переменных: FEATURES, TESLA и ONE_CAR_GARAGE .

Теперь в вашем рабочем случае вы ссылаетесь на Recreate.ONE_CAR_GARAGE. Это ссылка на статическое поле в Recreate, поэтому FEATURES, а затем ONE_CAR_GARAGE инициализируются. Затем во время инициализация ONE_CAR_GARAGE, TESLA инициализируется, так как ссылается на ее класс перечисления. Все хорошо.

Однако, если мы слишком рано ссылаемся на перечисление, мы делаем вещи в неправильном порядке. Recreate.Car.TESLA получает ссылку, поэтому TESLA получает инициализацию. TESLA ссылается на FEATURES, поэтому Recreate должен быть инициализирован. Это заставляет FEATURES и ONE_CAR_GARAGE инициализироваться до того, как TESLA закончит существующие.

Это скрытая зависимость, которая вас трогает. Recreate.Car зависит от Recreate, который зависит от Recreate.Car. Перемещение поля ONE_CAR_GARAGE в класс Garage приведет к тому, что он не будет инициализирован с помощью FEATURES и устранит вашу проблему.

2
ответ дан Silvio Mayolo 17 August 2018 в 13:42
поделиться
  • 1
    Ничего себе, я думаю, что я узнал что-то новое о статическом вложенном классе. – Jai 13 July 2018 в 05:03
  • 2
    Я думаю, было бы хорошо иметь сводку того, что может и не может быть сделано при использовании вложенных классов. – Jai 13 July 2018 в 05:09
Другие вопросы по тегам:

Похожие вопросы: