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 и его свойствам еще до создания объекта?
Ответы
Ответ 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;
}
}
}