Доступ к членам экземпляра в конструкторах

Я читал в книге, что члены экземпляра доступны только после запуска суперконструктора.

Я наткнулся на следующий код:

class Parent {

    Parent() {
        printIt();
    }

    void printIt() {
        System.out.println("I'm in a overridden method. Great.");
    }
}

class Child extends Parent {

    int i = 100;

    public static void main(String[] args) {
        Parent p = new Child();
        p.printIt();
    }

    void printIt() {
        System.out.print(i + " ");
    }
}

и печатает:

0 100

Мой вопрос будет:

Если члены экземпляра доступны только после запуска суперконструктора, то почему при выполнении метода printIt() класса Parent (который на самом деле является printIt() Child из-за полиморфизма) он смог получить доступ к неинициализированному переменная экземпляра i Child, даже если конструктор Parent еще не завершил выполнение?

Что мне не хватает?


person amor214    schedule 27.08.2012    source источник


Ответы (5)


Я читал в книге, что члены экземпляра доступны только после запуска суперконструктора.

Ваша книга неверна (если это действительно так). Они доступны в любое время после начала строительства. Однако они не инициализируются до тех пор, пока не запустится суперконструктор. То, что вы напечатали, было значением по умолчанию: null, zero или false.

person user207421    schedule 27.08.2012
comment
Они доступны в любое время после начала строительства. Однако они не инициализируются до тех пор, пока не запустится суперконструктор. --- Отличный момент. +1 - person Anuj Balan; 27.08.2012

он смог получить доступ к неинициализированной переменной экземпляра i Child, даже если конструктор Parent еще не завершил выполнение?

Вы смогли получить к нему доступ, но до того, как он был инициализирован (что обычно не требуется).

«Пробел» для переменной уже на месте (в конце концов, у вас есть экземпляр), но код, который инициализирует ее правильным начальным значением, еще не запущен. Так что все это будет null, false и 0.

В результате метод класса ("printIt") вызывается в неудобной точке жизненного цикла объекта (до запуска инициализаторов на "полуфабрикатном" экземпляре). Вот что хотел сказать предупреждение, которое вы прочитали.

person Thilo    schedule 27.08.2012

Я думаю, что ваш пример вводит вас в заблуждение. На самом деле суперконструктор запускается раньше, и вы можете увидеть это на модифицированном примере, как показано ниже. Также в качестве пояснения: значения членов доступны, но они могут быть еще не инициализированы.

class Parent {

    int i = 0;

    Parent() {
        i = 1;
        printIt();
    }

    void printIt() {
        System.out.println("I'm in a overridden method. Great. i = " + i);
    }
}

class Child extends Parent {
    public static void main(String[] args) {
        Parent p = new Child();
        p.printIt();
    }

    void printIt() {
        System.out.print(i + " ");
    }
}
person auselen    schedule 27.08.2012

Переопределение происходит в вашем коде. Объект учитывается во время выполнения. Итак, была вызвана функция printIt() для Child. В настоящее время значение «i» неизвестно, но имеет значение по умолчанию «0», так как это переменная экземпляра. Как только это будет сделано, вызывается p.printIt(), вызывается printIt() Child, и к этому времени считывается int i=100, и он печатает 100.

Таким образом, вывод должен и будет 0 100

person Anuj Balan    schedule 27.08.2012

Поля в объекте инициализируются значением по умолчанию, равным нулю или 0, когда объект создается впервые, затем фактически запускается ваш конструктор, который вызывает суперконструктор в качестве своего первого шага.

К сожалению, вы не можете обойти это, написав свой конструктор как

Child() {
  i=100;
  super();
}

И, не имея возможности сделать это, невозможно установить дочернее поле i до того, как оно будет использовано в вызове метода переопределения из родительского конструктора.

Однако стоит знать несколько способов обойти это:

Подход заключается в том, чтобы скрыть i за абстрактным геттером и предоставить статическую фабричную функцию, которая создает новые экземпляры, переопределяющие getI.

public class Child extends Parent {

   protected abstract getI();

   @Override void printIt() {
     System.out.print("i = " + i);
   }

   static Child create(final int i) {
      return new Child() {
         int getI() { return i; }
      }
   }
}

Child child = Child.create(100);

Другой подход состоит в том, чтобы отделить printIt от иерархии Родитель/Дочерний элемент. Затем вы можете создать принтер до вызова родительского конструктора. (Часто этот трюк можно использовать, чтобы полностью выпотрошить Child, оставив вам только класс Parent и компоненты — то есть вы в конечном итоге используете композицию, а не наследование.)

class Parent {
   public interface Printer {
     void printIt();
   }

   public class DefaultPrinter extends Printer {
     @Override void printIt() { 
       System.out.println("Default Printer...");
     }
   }

   Parent() {
     this(new DefaultPrinter());
   }

   Parent(Printer p ) {
     this.printer = p;
     printIt();
   }

   void printIt() {
     p.printIt();
   }
}

public class Child extends Parent {
   public class ChildPrinter implements Parent.Printer {
     final int i = 100;
     @Override void printIt() {
       System.out.println("i = "+i);
     }
   }

   Child() {
     super( new Printer() );
   }
}
person Michael Anderson    schedule 29.07.2014