实例变量初始化中的问题

时间:2010-07-20 09:26:58

标签: java instance instance-variables

下面是一些示例代码,

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的实例变量已初始化?

7 个答案:

答案 0 :(得分:8)

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

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

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

  1. 首先调用Base构造函数。
  2. lookup()被调用,它使用Derived
  3. 中的实现
  4. num返回,这是此时的默认值,因为Derived的构造函数和初始值设定项尚未运行
  5. val设置为0.
  6. 正在运行Derived初始值设定项和构造函数 - 从点开始调用lookup将返回10.
  7. 一般来说,出于这个原因从构造函数调用非final方法是个坏主意,许多静态分析工具会警告你不要这样做。它类似于在构造期间让对象引用泄漏,你最终会得到一个使类级别不变量无效的实例(在你的情况下,Derived的num是“总是”10但是在某些点它可以被看作是0 )。

    编辑:请注意,对于此特定案例,如果没有任何其他代码,您可以通过使num成为常量来解决问题:

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

    这实际上会做你想要的,因为静态初始化器在加载类时运行(必须在调用构造函数之前发生)。但是,这确实适用于:

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

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

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

答案 1 :(得分:4)

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

通常,在初始化派生实例字段之前调用基础构造函数。

答案 2 :(得分:2)

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

  • 调用派生的构造函数
    • 基础构造函数被称为第一个操作
    • 基础构造函数调用查找
  • 派生的构造函数继续并且初始化数量为10

由于在基础构造函数调用lookup时子类构造函数未完成,因此该对象尚未完全初始化,并且lookup将返回num字段的默认值。

答案 3 :(得分: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(),由于1而未设置val,因此得到0而不是10。

答案 4 :(得分:2)

为什么在构建基类时无法访问子类字段,已经有很多很棒的答案,但我认为你要求如何:a这样的工作解决方案:

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

答案 5 :(得分:1)

您在lookup()类中有覆盖方法Derived,因此当调用Base构造函数时,它会调用Derived的方法,其中正文为return num 。在Base初始化时,num的{​​{1}}实例变量尚未初始化且为0.这就是为什么val在Derived中被赋值为0的原因。

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

Base

答案 6 :(得分:1)

当构造函数调用此代码时,下面的代码正在返回0(通过查看程序,你会期望10)。原因很简单,num尚未初始化,而父类调用此方法。

public int lookup() {
    return num;
}