Проблема в инициализации переменной экземпляра - PullRequest
3 голосов
/ 20 июля 2010

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

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 инициализируются при вызове его метода?

Ответы [ 7 ]

8 голосов
/ 20 июля 2010

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

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

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

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

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

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

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

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

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

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

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

4 голосов
/ 20 июля 2010

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

В общем случае базовый конструктор вызывается до инициализации производных полей экземпляра.

2 голосов
/ 20 июля 2010

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

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() ...

2 голосов
/ 20 июля 2010

Давайте возьмем это медленно:

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, он связывается с конструктором Base, который вызывает метод поиска Derived, но num не был установлен, поэтому val остается неинициализированным.

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

2 голосов
/ 20 июля 2010

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

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

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

1 голос
/ 20 июля 2010

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

public int lookup() {
    return num;
}
1 голос
/ 20 июля 2010

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

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

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