代码之家  ›  专栏  ›  技术社区  ›  Zak

实例变量初始化问题

  •  3
  • Zak  · 技术社区  · 16 年前

    这是一些示例代码,

    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//由于lookup()被重写,我期望值为10,但不是0!有人能解释清楚吗?

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

    7 回复  |  直到 16 年前
        1
  •  8
  •   Andrzej Doyle    16 年前

    首先,由于缺少 someLookup 方法。

    总之,我认为您的问题在于,由于构造函数的层次结构运行方式,您的期望是无效的。

    超类“constructor始终在子类之前运行”,这包括子类“变量的初始值设定项(实际上作为构造函数的一部分运行)。因此,当您创建 Derived ,发生以下情况:

    1. 这个 Base 首先调用构造函数。
    2. lookup() 调用,它使用 衍生的 .
    3. num 返回, 这是此时的默认值,因为派生的构造函数和初始值设定项尚未运行 .
    4. val 设置为0。
    5. 这个 衍生的 初始值设定项和构造函数是运行-调用 lookup 从 这 上一点返回10。

    通常,出于这个原因从构造函数调用非最终方法是一个坏主意,许多静态分析工具都会警告您不要这样做。它类似于让对象引用在构造期间泄漏,您可以最终得到一个使类级不变量无效的实例(在您的例子中,派生的 号码 是“始终”10,但在某些点上可以看到为0)。

    编辑:请注意 特定的 如果没有任何附加代码,您可以通过 号码 常数:

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

    这实际上会满足您的需要,因为静态初始值设定项是在加载类时运行的(这必须在调用构造函数之前发生)。但是,这并不意味着:

    a)类的所有实例共享相同的 号码 变量; b) 号码 从不需要改变(如果这是真的,则(a)自动为真)。

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

    我在这里把它包括在比较和兴趣中,不是因为它是一个解决这个“问题”的通用方法(因为它不是)。

        2
  •  4
  •   andrewmu    16 年前

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

    一般来说,在初始化派生实例字段之前调用基构造函数。

        3
  •  2
  •   Jörn Horstmann    16 年前

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

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

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

        4
  •  2
  •   Cambium    16 年前

    让我们慢慢来:

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

    步骤1,在设置num=10之前,调用派生的(默认)构造函数,它链接到base的构造函数,后者调用派生的查找方法,但num尚未设置,因此val保持未初始化状态。

    步骤2,调用d.value(),它属于base,val由于1而未设置,因此得到0而不是10。

        5
  •  2
  •   Andreas Dolk    16 年前

    已经有很多很好的答案了 为什么? 在构造基类时不能访问子类字段,但我认为您要求 怎样 :此类问题的有效解决方案:

    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() 基类和基类上的方法可能未正确初始化。但是有了标志和一些异常,我们可以在运行时提醒程序员他应该调用 () …

        6
  •  1
  •   Sergii Pozharov    16 年前

    您有overriden方法 lookup() 在 Derived 类,所以当 Base 构造函数被调用,它从 衍生的 哪个身体是 return num . 当时 基地 初始化 num 的实例变量 衍生的 尚未初始化,为0。这就是为什么Val在 基地 .

    如果我正确理解了你的意图,你应该改变 value 方法在 基地 成为:

    public int value() {
    return lookup();
    }
    
        7
  •  1
  •   bragboy    16 年前

    当构造器调用此代码时,下面的代码将返回0(通过查看程序可以预期为10)。简单的原因是num尚未初始化,父类调用此方法。

    public int lookup() {
        return num;
    }