воскресенье, 23 января 2011 г.

Java: Вызов переопределенных методов в конструкторе

Подобная проблема, по-моему описывалась Блохом, но я изложу ее на русском и со своей стороны. Имеем родительский класс с полем, значение которого инициализируется в конструкторе (логика обычно простая). По правилам хорошего тона эта логика вынесена в отдельный метод. Поле после инициализации больше не используется для записи и поэтому final.
Спустя какое-то время появляется наследник класса у которого логика инициализации поля field другая. Для этого модификатор доступа меняется на protected, чтобы дать возможность наследнику заложить туда свою логику. Итак, получаем:
  1. static class Parent {
  2.         private final String field;
  3.  
  4.         Parent() {
  5.             field = createField();
  6.         }
  7.  
  8.         protected String createField() {
  9.             return "Parent-impl";
  10.         }
  11.  
  12.         @Override
  13.         public String toString() {
  14.             return getClass().getSimpleName() + field;
  15.         }
  16.     }
  17.  
  18.  
  19.     static class Child extends Parent {
  20.         private final String leftPart, rightPart;
  21.  
  22.         Child(String left, String right) {
  23.             leftPart = left;
  24.             rightPart = right;
  25.         }
  26.  
  27.         @Override
  28.         protected String createField() {
  29.             return leftPart + rightPart;
  30.         }
  31.     }
  32.  
  33.     public static void main(String[] args) {
  34.         System.out.println(new Child("AB", "CD"));

По результатам кажется что main должен вернуть ABCD. Однако это не так. Проблема в том, что в логике переопределенного метода createField() не должны участвовать состояния как наследника, так и родителя. Проще говоря можно использовать только константы (statiс final), хотя и с ними надо быть осторожным. Почему? Дело в том, что на момент вызова метода поля его + родительские поля создаваемого экземпляра еще не до конца созданы. В данном примере в момент вызова Child.createField() поля leftPart и rightPart ещё не проинициализированы (хотя они и final) и равны null.

Как теперь это исправить?
Есть несколько способов, я предпочитаю работать вместо поля с методом и как следствие вынести инициализацию в "ленивую" секцию. Как-то так:
  1. static class Parent {
  2.         private final String field;
  3.  
  4.         Parent() {
  5.             field = createField();
  6.         }
  7.  
  8.         private String createField() {
  9.             return "Parent-impl";
  10.         }
  11.  
  12.         protected String getField() {
  13.             return field;
  14.         }
  15.  
  16.         @Override
  17.         public String toString() {
  18.             return getClass().getSimpleName() + getField();
  19.         }
  20.     }
  21.  
  22.  
  23.     static class Child extends Parent
  24.  
  25.     {
  26.         private final String leftPart, rightPart;
  27.  
  28.         Child(String left, String right) {
  29.             leftPart = left;
  30.             rightPart = right;
  31.         }
  32.  
  33.         @Override
  34.         protected String getField
  35.                 () {
  36.             return leftPart + rightPart;
  37.         }
  38.     }
  39.  
  40.     public static void main(String[] args) {
  41.         System.out.println(new Child("AB", "CD"));

1 комментарий:

  1. В С++ помнится очень разочаровывало то что из конструктора нельзя вызывать виртуальные методы. Там по другому приходилось исхитряться, например, закрывать конструктор и добавлять к класс статический "метод создания"

    ОтветитьУдалить