Почему NullPointerException?
У меня есть абстрактный класс и производный класс.
Посмотрите на предоставленный код: -
public abstract class Parent{
public Parent(){
init();
}
public abstract void init();
}
public class Child extends Parent{
private String mTitle = null;
public Child(){
super();
System.out.println(mTitle.toString());
}
public void init(){
mTitle = "It' a test";
}
}
Когда я исполню вышеуказанный код, он выкинет NullPointerException при печати значения mTitle.
Если вы проверите код в конструкторе родителя, я вызвал абстрактный метод, который будет вызван методом init производного класса,
В абстрактном методе я инициализирую значение значения mTitle как = "Это тест";
После вызова родительского конструктора производного класса необходимо вызвать System.out.println.
Если это так, то почему он бросает исключение NullPointerException.
Но, если я просто оставлю задание mTitle, он не будет выбрасывать исключение, например: -
private String mTitle;
Если инициализация переменной возникает при вызове contruct класса, и мы знаем, что по умолчанию глобальный объект инициализируется в null.
Но в этом случае он не будет выбрасывать исключение.
Ответы
Ответ 1
Как и в JLS §12.5 (Создание экземпляров нового класса), следующая процедура используется при создании экземпляра:
-
Назначьте аргументы для конструктора вновь созданным переменным параметра для этого вызова конструктора.
-
Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора в том же классе (с использованием этого), то затем оценивайте аргументы и обрабатывайте вызов конструктора рекурсивно, используя эти пять шагов. Если вызов конструктора завершается внезапно, то эта процедура завершается внезапно по той же причине; в противном случае, перейдите к шагу 5.
-
Этот конструктор не начинается с явного вызова конструктора другого конструктора в том же классе (используя это). Если этот конструктор относится к классу, отличному от Object, то этот конструктор начнет с явного или неявного вызова конструктора суперкласса (используя super). Оцените аргументы и обработайте вызов конструктора суперкласса рекурсивно, используя эти пять шагов. Если вызов конструктора завершается внезапно, то эта процедура завершается внезапно по той же причине. В противном случае перейдите к шагу 4.
-
Выполните инициализаторы экземпляра и инициализаторы переменных экземпляра для этого класса, присваивая значения инициализаторов переменной экземпляра соответствующим переменным экземпляра в порядке слева направо, в котором они отображаются в текстовом виде в исходном коде для класс. Если выполнение любого из этих инициализаторов приводит к исключению, то никакие новые инициализаторы не обрабатываются, и эта процедура завершается внезапно с тем же исключением. В противном случае перейдите к шагу 5.
-
Выполните оставшуюся часть тела этого конструктора. Если это выполнение завершается внезапно, то эта процедура завершается внезапно по той же причине. В противном случае эта процедура завершится нормально.
Это означает, что ваш вызов super()
и последующий вызов переопределенного метода init()
выполняются до инициализации переменной экземпляра с помощью null
, которая отбрасывает результат метода init
и перезаписывает любое значение, являющееся присвоенный mTitle
значением null
.
Это приводит к следующему Золотому правилу: никогда не вызывайте неконфиденциальные методы в конструкторе!
Ответ 2
Согласно разделу 12.5 JLS, конструктор суперкласса будет запускаться перед инициализатором для mTitle
, что означает, что он будет установлен вернитесь к null
после того, как он установлен на "It a test"
.
Ответ 3
Согласно раздел 12.5 JLS, конструктор суперкласса будет выполняться перед конструктором производного класса.
Инициализация глобальной переменной - это вызов во время вызова constuctor.
Поэтому, когда конструктор суперкласса вызывает абстрактный метод и устанавливает значение mTitle в реализации метода abstarct, он будет устанавливать первое значение this = "это тест".
После завершения суперклассического класса он вызовет конструктор производного класса и
когда вызов конструктора производного класса сначала инициализирует свою глобальную переменную, которая устанавливает значение mTitle в значение null.
Как упоминается в коде
private String mTitle = null;
Но во втором случае mTitle не присваивают никакого значения в коде как упоминание
private String mTitle;
поэтому он примет значение по умолчанию, которое мы назначили в реализованном abstarct-методе init, "это тест", поэтому он не будет генерировать исключение.