Проблема в инициализации переменной экземпляра

Вот пример кода,

class Base
{
  private int val;

  Base() {
  val = lookup();
  }

  public int lookup() {
    //Perform some lookup
  // int num = someLookup();
  return 5;
  }

  public int value() {
  return val;
  }
}

class Derived extends Base
{
  private int num = 10;

  public int lookup() {
  return num;
  }
}


class Test
{
  public static void main(String args[]) {

  Derived d = new Derived();
  System.out.println("d.value() returns " + d.value());

  }
}

output: d.value() возвращает 0//Я ожидал 10, поскольку lookup() переопределен, но не 0! может кто-нибудь прояснить это?

Инициализация переменных экземпляра Derived не выполнялась в тот момент, когда выполняется его метод поиска. Как убедиться, что переменные экземпляра Derived инициализируются при вызове метода?

Ответ 1

Хорошо для начала, этот код не компилируется из-за отсутствия метода someLookup.

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

Конструктор суперкласса всегда запускается перед подклассом, и это включает инициализаторы для переменных подкласса (которые действительно выполняются как часть конструктора). Итак, когда вы создаете свой экземпляр Derived, происходит следующее:

  • Сначала запускается конструктор Base. Вызывается
  • lookup(), который использует реализацию в Derived.
  • num, , который является значением по умолчанию в этой точке, потому что Derived конструктор и инициализаторы не были запущены.
  • val установлено значение 0.
  • Инициализаторы и конструкторы Derived запускаются при вызове lookup с этой точки после возвращения 10.

В общем, это плохая идея вызывать неконкретный метод из конструктора именно по этой причине, и многие инструменты статического анализа будут предупреждать вас об этом. Это похоже на то, чтобы утечка объектов во время построения протекала, вы можете получить экземпляр, который делает недействительными инварианты на уровне класса (в вашем случае Derived num является "всегда" 10, но в некоторых случаях это может быть 0).

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

class Derived extends Base
{
  private static final int num = 10;
  ...

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

a) все экземпляры класса используют одну и ту же переменную num;  b) num никогда не нужно изменять (если это правда, тогда (a) истинно автоматически).

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

Я включаю это здесь для сравнения и интереса, а не потому, что это обходной путь к этой "проблеме" в общем смысле (потому что это не так).

Ответ 2

Причина, по которой вы получаете 0, - это то, что вызываются конструкторы Base (и вызывается поиск в Derived) до того, как 10 будет присвоено num в Derived.

Как правило, базовый конструктор вызывается до того, как инициализируются производные поля экземпляра.

Ответ 3

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

  • Вызывается производный конструктор
    • Базовый конструктор называется его первым действием
    • Поиск вызова конструктора базы данных
  • Производный конструктор продолжается и инициализирует num до 10

Так как конструктор подкласса не завершен, когда базовый конструктор вызывает поиск, объект еще не полностью инициализирован, а lookup возвращает значение по умолчанию для поля num.

Ответ 4

Возьмите его медленно:

class Test
{
  public static void main(String args[]) {
  // 1
  Derived d = new Derived();
  // 2
  System.out.println("d.value() returns " + d.value());    
  }
}

Шаг 1, вы вызываете конструктор (по умолчанию) на Derived, перед установкой num = 10 он соединяется с базовым конструктором, который вызывает метод Derived lookup, но num не был установлен, поэтому val остается неинициализированным.

Шаг 2, вы вызываете d.value(), который принадлежит Base, а val не задан из-за 1, и поэтому вы получаете 0 вместо 10.

Ответ 5

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

public abstract class Animal {
  public Animal() {
    System.println(whoAmI());
  }
  public abstract String whoAmI();
}

public Lion() extends Animal {
  private String iAmA = "Lion";
  public Lion(){super();}
  public String whoAmI() {return iAmA;}
}

Практический способ - ввести метод init() в базовый класс, вызвать его из конструктора подкласса, например:

public abstract class Animal {
  private boolean isInitialized = false;
  public Animal() {}
  void init() {
    isInitialized = true;
    System.out.println(whoAmI());
  }
  public abstract String whoAmI();
  public void someBaseClassMethod() {
    if (!isInitialized)
      throw new RuntimeException("Baseclass has not been initialized");
    // ...
  }
}

public Lion() extends Animal {
  private String iAmA = "Lion";
  public Lion() {
    super();
    init();
  }
  public String whoAmI() {return iAmA;}
}

Проблема только в том, что вы не можете заставить подклассы вызывать метод init() в базовом классе, а базовый класс может быть неправильно инициализирован. Но с флагом и некоторыми исключениями мы можем напомнить программисту во время выполнения, что он должен был называть init()...

Ответ 6

В классе Derived вы переопределили метод lookup(), поэтому при вызове конструктора Base он вызывает метод из Derived, тело которого return num. Во время инициализации Base переменная экземпляра num для Derived еще не инициализирована и равна 0. Именно поэтому val присваивается 0 в Base.

Если я правильно понял ваши намерения, вы должны изменить метод value в Base следующим образом:

public int value() {
return lookup();
}

Ответ 7

Следующий фрагмент кода содержит 0 (вы ожидали бы 10, посмотрев на программу), когда конструктор делает вызов. Простая причина в том, что num еще не инициализирован, и родительский класс вызывает этот метод.

public int lookup() {
    return num;
}