2009-06-08 2 views
2

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

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

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

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

  • Я предполагаю, что вы упрощены для примера, но вызов до .clear() ничего не делает в этом примере; вы можете просто удалить его.
  • Трудно сказать, не видя фактической программы в целом, но вы можете кататься с помощью назначения переменной HashSet вместо вызова .clear().
+0

Это правильно, ясный был просто примером для исключения. – danieljimenez

+0

Я принимаю этот ответ, потому что я не знал, что это рекомендуется. Я включил инспекции в IntelliJ, чтобы помешать нам сделать это в будущем. Спасибо Хэнку! – danieljimenez

+0

вы не можете назначить конечную переменную (variableOfDoom в этом случае) новую HashSet вне любого конструктора (в этом случае метод dontPanic()). Это то, о чем упоминается ваш второй пункт? – neesh

7

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

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

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

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

+0

+1 быстрее меня за 30 секунд. –

+0

Да, я понимаю проблему и поток exec, я просто ищу предложения о лучших практиках, чтобы избежать этого. – danieljimenez

+0

Тогда это будет «не вызывать неконкретные не конечные методы в конструкторе». –

2

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

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

Единственный выход из этого - отделить ваш вызов от dontPanic вне конструктора.

0

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

Без знаю, что вы на самом деле пытаетесь достичь, его трудно Recommand «лучший способ решить эту проблему», но вот идти:

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(); 
    } 
} 
1

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

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

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

0

быстро & грязный раствор (не очень хорошо, я знаю)

... 
System.out.println("sub don't panic"); 
// must test if object fully initialized, method called in constructor 
if (variableOfDoom != null) { 
    variableOfDoom.clear(); 
} 
+0

И variableOfDoom все еще может быть окончательным. –

+0

Почему downvote (через 6 месяцев)? –

1

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

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

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! 
    } 
} 

Обратите внимание, что вы не можете иметь variableOfDoom быть окончательным.

+0

Не знаете, почему вы создали бы HashSet, чтобы его можно было очистить. Я хотел бы проверить нулевое значение и предположить, что его нужно очистить, если он не был инициализирован. –

Смежные вопросы