41

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

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

4

6 回答 6

31

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

根据语言规范,实例变量在super()调用之前甚至不会被初始化。

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

  1. 将构造函数的参数分配给此构造函数调用的新创建的参数变量。
  2. 如果此构造函数以同一类中另一个构造函数的显式构造函数调用(第 8.8.7.1 节)开始(使用 this),则评估参数并使用这五个相同的步骤递归地处理该构造函数调用。如果该构造函数调用突然完成,则此过程出于相同原因而突然完成;否则,继续执行步骤 5。
  3. 此构造函数不以显式构造函数调用同一类中的另一个构造函数开始(使用 this)。如果此构造函数用于 Object 以外的类,则此构造函数将以显式或隐式调用超类构造函数(使用 super)开始。使用这五个相同的步骤递归地评估超类构造函数调用的参数和过程。如果该构造函数调用突然完成,则此过程出于相同的原因突然完成。否则,继续执行步骤 4。
  4. 执行该类的实例初始化程序和实例变量初始化程序,将实例变量初始化程序的值分配给相应的实例变量,按照它们在源代码中以文本形式出现的从左到右的顺序。如果执行这些初始化程序中的任何一个导致异常,则不会处理更多初始化程序,并且此过程会突然完成相同的异常。否则,继续执行步骤 5。
  5. 执行此构造函数的其余部分。如果该执行突然完成,则此过程出于同样的原因突然完成。否则,此过程正常完成。
于 2013-03-28T13:05:33.227 回答
15

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

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

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

于 2013-03-28T13:19:53.903 回答
9

我有办法做到这一点。

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

    ...
}
于 2013-09-06T12:46:58.513 回答
8

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

但是有一些解决方法。一种是创建一个工厂类,它获取值并将其传递给 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;
    }
    ...
}
于 2013-03-28T13:17:56.570 回答
1

Java 语言规范(第 8.8.7 节)禁止这样做:

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

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

构造函数体:

{ ExplicitConstructorInvocationopt BlockStatementsopt }
于 2013-03-28T13:06:15.997 回答
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;
        }
    }
}

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

于 2020-07-23T10:15:57.207 回答