NullPointerException с методом переопределения - PullRequest
2 голосов
/ 08 июня 2009

Кто-нибудь может прокомментировать свое мнение о лучшем способе решения следующей проблемы? Я пытался инициализировать блоки и инициализировать поле в конструкторе, но ни один из них не вызывается до того, как переопределенный метод попытается его очистить.

 class TestRunner {
        public static void main(String[] args) {
            ftw ftw = new ftw();
            ftw.dontPanic();
        }
    }

    class wtf {

        wtf(){
            this.dontPanic();
        }

        void dontPanic() {
            System.out.println("super don't panic");
        }
    }

    class ftw extends wtf {
        final HashSet variableOfDoom = new HashSet();

        ftw(){
            super();
        }

        void dontPanic() {
            super.dontPanic();
            System.out.println("sub don't panic");
            variableOfDoom.clear(); //This line prints a null pointer exception, because the field hasn't been intialized yet.
        }
    }

Ответы [ 7 ]

7 голосов
/ 08 июня 2009

Вот проблема: ваш поток выполнения выглядит следующим образом:

main
->ftw()
  ->wtf()
    ->wtf.dontPanic()

Но поскольку вызовы методов определяются во время выполнения в Java, ftw.dontPanic() - это метод, который действительно вызывается. И variableOfDoom не будет установлен до завершения конструктора, но конструктор никогда не завершится из-за исключения.

Решение состоит в том, чтобы не выполнять работу в вашем конструкторе (по крайней мере, не с не- private не- final методами).

7 голосов
/ 08 июня 2009

Вот почему они рекомендуют не вызывать не-final методы внутри конструктора. private методы также безопасны, так как их нельзя переопределить.

Предполагая, что по каким-то причинам вы не можете следовать этому здравому совету, вот некоторые другие наблюдения, которые могут помочь:

  • Я предполагаю, что вы упростили ради примера, но вызов .clear() ничего не делает в этом примере; Вы можете просто удалить его.
  • Трудно сказать, не видя реальной программы в целом, но вы можете кататься на коньках, присвоив переменной новый HashSet вместо вызова .clear().
2 голосов
/ 08 июня 2009

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

Если ftw не расширяет wtf, тогда можно предположить, что любая инициализация, указанная в определении поля, будет выполнена до вызова конструктора. Однако, поскольку ftw расширяет wtf, wtf должен быть полностью инициализирован, прежде чем любая инициализация может произойти в ftw. Так как часть инициализации wtf зависит от переменнойOfDoom в подклассе, которая должна быть инициализирована, вы получаете исключение нулевого указателя.

Единственный выход из этого заключается в том, что вы можете отделить ваш вызов dontPanic от конструктора.

1 голос
/ 10 июня 2009

Во-первых, не делай этого.

Во-вторых, если вам абсолютно необходимо это сделать, сделайте следующее:

class wtf {

    wtf(){
            this.dontPanic();
    }

    void dontPanic() {
            System.out.println("super don't panic");
    }
}

class ftw extends wtf {
    HashSet _variableOfDoom; // underscore to remind you not to access it directly

    ftw(){
            super();
    }

    private HashSet getVariableOfDoom()
    {
        if (_variableOfDoom == null) _variableOfDoom = new HashSet();
        return _variableOfDoom;
    }

    void dontPanic() {
            super.dontPanic();
            System.out.println("sub don't panic");
            getVariableOfDoom().clear(); // FOR GREAT JUSTICE!
    }
}

Обратите внимание, что переменнаяOfDoom не может быть окончательной.

1 голос
/ 08 июня 2009

Вы можете заметить, что приватным методам не запрещается вызывать переопределения; поэтому даже в этом случае будьте осторожны при вызове всего, что переопределено конструкторами. В других языках рекомендуется избегать вызова каких-либо виртуальных методов из конструктора, поскольку объект может быть не полностью создан при вызове метода.

Альтернативой для рассмотрения является двухэтапное строительство. http://www.artima.com/forums/flat.jsp?forum=106&thread=106524

В книге «Руководство по проектированию .NET Framework» есть связанная идиома, которая называется «create-set-call». Создайте объект, установите некоторые свойства, вызовите несколько методов. Я видел, что такое использование иногда критиковалось как менее обнаруживаемое, чем простой вызов конструкции (т. Е. Просто создавай и используй).

0 голосов
/ 09 июня 2009

быстрое и грязное решение (не очень приятно, я знаю)

...
System.out.println("sub don't panic");
// must test if object fully initialized, method called in constructor
if (variableOfDoom != null) {
    variableOfDoom.clear();
}
0 голосов
/ 08 июня 2009

Не вызывайте не финальные методы из конструкторов - не все переменные экземпляра могут быть инициализированы - ваш код является классическим примером.

Не зная, чего вы на самом деле пытаетесь достичь, трудно рекомендовать «лучший способ решения проблемы», но вот что:

class TestRunner {
    public static void main(String[] args) {
            ftw ftw = new ftw();
            ftw.dontPanic();
    }
}

class wtf {

    wtf(){
            wtfDontPanic();
    }

    private final void wtfDontPanic() {
            System.out.println("super don't panic");
    }

    void dontPanic() {
            wtfDontPanic();
    }
}

class ftw extends wtf {
    final HashSet variableOfDoom = new HashSet();

    ftw(){
            super();
            ftwDontPanic();
    }

    private final ftwDontPanic() {
            System.out.println("sub don't panic");
            variableOfDoom.clear(); //This line prints a null pointer exception, because the field hasn't been intialized yet.
    }

    private void dontPanic() {
            super.DontPanic();
            ftwDontPanic();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...