实例变量初始化问题

发布于 2024-09-10 17:55:30 字数 731 浏览 7 评论 0原文

这是一些示例代码,

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 的实例变量在调用其方法时已初始化?

Heres some sample code,

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() returns 0 // I expected 10 as lookup() is overridden, but not 0! can someone clarify this?

The initialization of Derived's instance variables has not happened at the time its lookup method executes. How do I make sure the instance variables of Derived are initialized when its method is called?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

残花月 2024-09-17 17:55:30

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

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

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

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

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

编辑:请注意,对于这种特定情况,无需任何额外的代码,您可以通过将num设置为常量来解决问题:

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

这实际上会做您想要的事情,因为静态初始化程序在类加载时运行(必须在调用构造函数之前发生)。然而,这确实假设以下情况是可以的:

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

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

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

Well for a start, that code doesn't compile due to the lack of someLookup method.

Anyway, asides from that I believe your issue is that your expections are invalid because of the way constructors are run hierarchically.

A superclass' constructor is always run before the subclass', and this includes initializers for the subclass' variables (which are really run as part of the constructor). So, when you create your instance of Derived, the following happens:

  1. The Base constructor is invoked first.
  2. lookup() is called, which uses the implementation in Derived.
  3. num is returned, which is the default value at this point because Derived's constructor and initializers have not been run.
  4. val is set to 0.
  5. The Derived initializers and constructor are run - calling lookup from this point on will return 10.

In general, it's a bad idea to call a non-final method from a constructor for exactly this reason, and many static analysis tools will warn you against it. It's similar to letting object references leak during construction, you can end up with an instance that invalidates class-level invariants (in your case, Derived's num is "always" 10 yet it can be seen to be 0 at some points).

Edit: Note that for this particular case, without any additional code, you could resolve the issue by making num a constant:

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

This would actually do what you want, because the static initializer is run when the class is loaded (which has to happen before the constructors are called). This does however assume that it's fine for:

a) all instances of the class to share the same num variable;
b) num never needs to change (if this is true then (a) is true automatically).

In the exact code you've given this is clearly the case, but I expect you may be omitting extra functionality for brevity.

I include this here for comparison and interest, not because it's a workaround to this "issue" in a general sense (because it's not).

甜尕妞 2024-09-17 17:55:30

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

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

The reason you are getting 0 returned is that the constructors Base is being called (and calling lookup in Derived) before 10 is assigned to num in Derived.

To put generally, the base constructor is called before the derived instance fields are initialised.

分分钟 2024-09-17 17:55:30

对于为什么在构造基类时无法访问子类字段已经有很多很好的答案,但我认为您要求如何:一个可行的解决方案像这样的事情:

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() 方法code> 基类上的方法,并且基类可能未正确初始化。但是通过标志和一些例外,我们可以在运行时提醒程序员他应该调用 init()...

There are a lot of great answers already on why you can't access subclass fields while constructing the base class, but I think you asked for a how: a working solution for something like this:

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;}
}

The practical way is to introduce an init() method on the base class an call it from the subclass's constructor, like:

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;}
}

Only problem is, you can't force subclasses to call the init() method on the base class and the base class might not be properly initialized. But with a flag and some exceptions we can remind the programmer at runtime that he should have called init()...

琉璃梦幻 2024-09-17 17:55:30

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

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

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

It is generally a bad idea to call methods in a constructor that can be overriden in a subclass. In your example the following happens:

  • Derived constructor is called
    • Base constructor is called as its first action
    • Base constructor calls lookup
  • Derived constructor continues and initialies num to 10

Since the subclass constructor is not finished when the base constructor calls lookup, the object is not yet completely initialized and lookup returns the default value of the num field.

沉鱼一梦 2024-09-17 17:55:30

让我们慢慢看:

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。

Let's take it slowly:

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

Step 1, you call the (default) constructor on Derived, before setting num = 10, it chains up to Base's constructor, which calls Derived's lookup method, but num has not been set, so val remains uninitialized.

Step 2, you call d.value(), which belongs to Base, and val is unset due to 1, and therefore you get 0 instead of 10.

坠似风落 2024-09-17 17:55:30

您已重写 Derived 类中的 lookup() 方法,因此当调用 Base 构造函数时,它会调用 Derived< 中的方法/code> 其中主体是return num。在 Base 初始化时,Derivednum 实例变量尚未初始化并且为 0。这就是为什么 val 被分配为 0 基础

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

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

You have overriden method lookup() in the Derived class, so when the Base constructor is called it calls the method from Derived which body is return num. At the time of Base initialization the num instance variable of the Derived is not yet initialized and is 0. That's why val is assigned to 0 in Base.

If I understood your intentions correctly, you should change the value method in Base to be:

public int value() {
return lookup();
}
冷血 2024-09-17 17:55:30

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

public int lookup() {
    return num;
}

The below piece of code is returing 0 (you would expect 10 by looking at the program) when the constructor makes a call to this. The simple reason is that num is not initialized yet and the parent class calls this method.

public int lookup() {
    return num;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文