在超级构造函数运行之前初始化字段?

时间:2013-03-28 12:58:35

标签: java inheritance constructor initialization constructor-chaining

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

即使是我能想到的最丑陋的黑客也被编译器拒绝了:

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

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

6 个答案:

答案 0 :(得分:26)

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

根据language specs,在进行super()调用之前,实例变量甚至都没有初始化。

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

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

答案 1 :(得分:11)

超级构造函数将在任何情况下运行,但由于我们正在谈论“最丑陋的黑客”,我们可以利用这个

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

我从不建议使用这些黑客。

答案 2 :(得分:6)

我有办法做到这一点。

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

    ...
}

答案 3 :(得分:4)

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

但有一些解决方法。一种是创建一个获取值的工厂类,并将其传递给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;
    }
    ...
}

答案 4 :(得分:1)

Java language specification (section 8.8.7)禁止使用

  

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

构造函数体应如下所示:

  

ConstructorBody:

{ ExplicitConstructorInvocationopt BlockStatementsopt }

答案 5 :(得分:0)

虽然不可能直接做到,但是您可以尝试使用嵌套对象来实现

以您的示例为例:

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

快乐的编码-可以破解,但可以工作:)记住将derivedObject定义为 LAST 变量