3

这是一些示例代码,

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

  }
}

输出:d.value() 返回 0 // 我希望 10 因为lookup() 被覆盖,但不是 0!有人可以澄清一下吗?

的实例变量的初始化Derived在其查找方法执行时尚未发生。如何确保在Derived调用其方法时初始化其实例变量?

4

7 回答 7

9

someLookup首先,由于缺少方法,该代码无法编译。

无论如何,除此之外,我认为您的问题是您的期望是无效的,因为构造函数是分层运行的。

超类的构造函数总是在子类之前运行,这包括子类变量的初始化程序(它们实际上作为构造函数的一部分运行)。因此,当您创建 的实例时Derived,会发生以下情况:

  1. 首先Base调用构造函数。
  2. lookup()被调用,它使用Derived.
  3. num返回,这是此时的默认值,因为 Derived 的构造函数和初始化程序尚未运行
  4. val设置为 0。
  5. Derived初始化程序和构造函数正在运行 -lookup从此时开始调用返回 10。

通常,正是出于这个原因,从构造函数中调用非 final 方法是一个坏主意,许多静态分析工具会警告您不要这样做。这类似于在构造过程中让对象引用泄漏,您最终可能会得到一个使类级不变量无效的实例(在您的情况下,Derived 的num“始终”为 10,但在某些时候它可以被视为 0)。

编辑:请注意,对于这种特殊情况,无需任何其他代码,您可以通过创建num常量来解决问题:

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

这实际上可以满足您的要求,因为静态初始化程序在加载类时运行(这必须在调用构造函数之前发生)。然而,这确实假设它适用于:

a) 类的所有实例共享同一个num变量;b)num永远不需要改变(如果这是真的,那么 (a) 自动为真)。

在您给出的确切代码中显然是这种情况,但我希望您可能会为了简洁而省略额外的功能。

我将其包括在这里是为了比较和感兴趣,而不是因为它是一般意义上的这个“问题”的解决方法(因为它不是)。

于 2010-07-20T09:40:27.533 回答
4

返回 0 的原因是构造函数 Base 在 10 分配给 Derived 中的 num 之前被调用(并在 Derived 中调用查找)。

概括地说,在初始化派生实例字段之前调用基本构造函数。

于 2010-07-20T09:36:03.330 回答
3

关于为什么在构造基类时不能访问子类字段,已经有很多很好的答案,但我认为你问了一个如何:这样的工作解决方案:

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

于 2010-07-20T10:05:33.487 回答
2

在构造函数中调用可以在子类中覆盖的方法通常是一个坏主意。在您的示例中,会发生以下情况:

  • 派生构造函数被调用
    • 基础构造函数被调用作为它的第一个动作
    • 基本构造函数调用查找
  • 派生构造函数继续并将 num 初始化为 10

由于基构造函数调用lookup时子类构造函数没有完成,所以对象还没有完全初始化,lookup返回num字段的默认值。

于 2010-07-20T09:43:48.153 回答
2

我们慢慢来:

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 步,调用属于 Base 的 d.value(),而 val 由于 1 而未设置,因此得到的是 0 而不是 10。

于 2010-07-20T09:48:28.553 回答
1

lookup()您在类中有重写方法Derived,因此当Base调用构造函数时,它会调用Derivedbody 所在的方法return num。在Base初始化时, 的num实例变量Derived尚未初始化,为 0。这就是为什么 val 在 中被赋值为 0 的原因Base

如果我正确理解您的意图,您应该将value方法更改Base为:

public int value() {
return lookup();
}
于 2010-07-20T09:38:58.487 回答
1

当构造函数调用 this 时,下面的代码将返回 0(通过查看程序,您会期望 10)。原因很简单,num 还没有初始化,父类调用了这个方法。

public int lookup() {
    return num;
}
于 2010-07-20T09:41:22.843 回答