【问题标题】:Initialize field before super constructor runs?在超级构造函数运行之前初始化字段?
【发布时间】:2013-03-28 12:58:35
【问题描述】:

在Java中,有没有办法在超级构造函数运行之前初始化一个字段?

即使是我能想到的最丑陋的 hack 也会被编译器拒绝:

class Base
{
    Base(String someParameter)
    {
        System.out.println(this);
    }
}

class Derived extends Base
{
    private final int a;

    Derived(String someParameter)
    {
        super(hack(someParameter, a = getValueFromDataBase()));
    }

    private static String hack(String returnValue, int ignored)
    {
        return returnValue;
    }

    public String toString()
    {
        return "a has value " + a;
    }
}

注意:当我从继承切换到委托时,问题就消失了,但我仍然想知道。

【问题讨论】:

  • 您是否要预初始化字段a
  • 我认为你做不到。在 super 调用之后,您在类中执行的任何初始化(即使它在构造函数之外)都会移动到每个构造函数。因此,超级构造函数总是在字段初始化之前运行。
  • @FredOverflow 因为a 只能在Derived 中访问,为什么它被初始化之前 super() 被调用?之后立即初始化它不会对您提供的示例产生影响(除非您从 Base 构造函数调用覆盖的方法,这开始闻起来很腥)。
  • Effective Java Item 17: "构造函数不能直接或间接调用可重写的方法(...)如果重写方法依赖于子类构造函数执行的任何初始化,则该方法不会表现如预期。"
  • Ugly Hack:直接在 java 字节码中创建派生类,如以下答案:stackoverflow.com/questions/3278865/…

标签: java inheritance constructor initialization constructor-chaining


【解决方案1】:

不,没有办法做到这一点。

根据language specs,在调用super() 之前,甚至不会初始化实例变量。

这些是在类实例创建的构造函数步骤中执行的步骤,取自链接:

  1. 将构造函数的参数分配给新创建的参数 此构造函数调用的变量。
  2. 如果此构造函数以显式构造函数调用开始 (第 8.8.7.1 节)同一类中的另一个构造函数(使用 this), 然后评估参数并处理构造函数调用 递归地使用这五个相同的步骤。如果那个构造函数 调用突然完成,则此过程完成 突然出于同样的原因;否则,继续第 5 步。
  3. 此构造函数不是以显式构造函数开头 调用同一类中的另一个构造函数(使用 this)。如果 this 构造函数用于 Object 以外的类,然后 this 构造函数将以显式或隐式调用 超类构造函数(使用 super)。评估论据和 使用递归处理超类构造函数调用 同样的五个步骤。如果该构造函数调用完成 突然,然后这个过程突然完成相同的 原因。否则,请继续执行第 4 步。
  4. 执行实例初始化器和实例变量初始化器 对于此类,分配实例变量的值 对应实例变量的初始化器,在 从左到右的顺序,它们以文本形式出现在源代码中 类的代码。如果执行这些初始化程序中的任何一个 导致异常,则不再处理任何初始化程序 这个过程突然完成,但有同样的例外。 否则,请继续执行第 5 步。
  5. 执行此构造函数的其余部分。如果那个执行 突然完成,那么这个过程突然完成 同样的原因。否则,此过程正常完成。

【讨论】:

  • @mkilic:很确定
【解决方案2】:

正如其他人所说,您不能在调用超类构造函数之前初始化实例字段。

但有一些解决方法。一种是创建一个工厂类,它获取值并将其传递给 Derived 类的构造函数。

class DerivedFactory {
    Derived makeDerived( String someParameter ) {
        int a = getValueFromDataBase();
        return new Derived( someParameter, a );
    }
}


class Derived extends Base
{
    private final int a;

    Derived(String someParameter, int a0 ) {
        super(hack(someParameter, a0));
        a = a0;
    }
    ...
}

【讨论】:

  • 这是我认为最好的解决方案。我唯一要改变的是,我将在 Derived 类中放置一个静态工厂方法。
【解决方案3】:

超级构造器在任何情况下都会运行,但由于我们谈论的是“最丑陋的黑客”,我们可以利用这一点

public class Base {
    public Base() {
        init();
    }

    public Base(String s) {
    }

    public void init() {
    //this is the ugly part that will be overriden
    }
}

class Derived extends Base{

    @Override
    public void init(){
        a = getValueFromDataBase();
    }
} 

我从不建议使用这些技巧。

【讨论】:

  • +1 - 你是对的。伊克。我在想C++。仍然 - 我认为黑客没有必要 - 请参阅我的答案。
  • 这并不是真正的 超级构造函数运行之前,正如 OP 所要求的那样。可能是你能得到的最接近的。
  • @Keppil 我在回答“超级构造函数无论如何都会运行”中提到,这只是在派生类中初始化变量的技巧
【解决方案4】:

我有办法做到这一点。

class Derived extends Base
{
    private final int a;

    // make this method private
    private Derived(String someParameter,
                    int tmpVar /*add an addtional parameter*/) {
        // use it as a temprorary variable
        super(hack(someParameter, tmpVar = getValueFromDataBase()));
        // assign it to field a
        a = tmpVar;
    }

    // show user a clean constructor
    Derived(String someParameter)
    {   
        this(someParameter, 0)
    }

    ...
}

【讨论】:

    【解决方案5】:

    Java language specification (section 8.8.7)禁止:

    构造函数主体的第一条语句可能是显式的 调用同一类的另一个构造函数或直接 超类。

    构造函数主体应该如下所示:

    构造函数体:

    { ExplicitConstructorInvocationopt BlockStatementsopt }
    

    【讨论】:

      【解决方案6】:

      虽然不能直接做,但是可以尝试用嵌套对象做

      以你的例子为例:

      open class Base {
          constructor() {
              Timber.e(this.toString())
          }
      }
      
      class Derived {
          val a = "new value"
      
          val derivedObject : Base =  object : Base() {
              override fun toString(): String {
                   return "a has value " + a;
              }
          }
      }
      

      快乐的编码 - 它是 hack 但有效 :) 记得将 derivedObject 定义为 LAST 变量

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-14
        • 1970-01-01
        • 2012-06-30
        • 2011-03-28
        • 1970-01-01
        相关资源
        最近更新 更多