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

Java 在超级构造函数运行之前初始化字段?,java,inheritance,constructor,initialization,constructor-chaining,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) { s

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

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

不,没有办法做到这一点

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

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

  • 将构造函数的参数分配给新创建的参数 此构造函数调用的变量
  • 如果此构造函数以显式构造函数调用开始 (§8.8.7.1)同一等级的另一个建造商(使用此), 然后评估构造函数调用的参数和过程 递归地使用同样的五个步骤。如果这个构造器 调用突然完成,然后此过程完成 出于同样的原因突然;否则,继续执行步骤5
  • 此构造函数不以显式构造函数开头 调用同一类中的另一个构造函数(使用此函数)。如果 此构造函数用于对象以外的类,则 构造函数将以显式或隐式调用 超类构造函数(使用super)。评估论点和结论 使用递归方式调用超类构造函数的进程 这五个步骤是相同的。如果构造函数调用完成 突然,则此过程突然完成,原因相同 原因。否则,继续执行步骤4
  • 执行实例初始值设定项和实例变量初始值设定项 对于此类,指定实例变量的值 在 它们在源代码中以文本形式出现的从左到右顺序 类的代码。如果执行任何这些初始值设定项 结果出现异常,则不再处理其他初始值设定项 这个过程突然完成,但也有同样的例外。 否则,继续执行步骤5
  • 执行此构造函数主体的其余部分。如果那次处决 突然完成,则此过程对于 同样的原因。否则,此过程将正常完成
  • 以下条款禁止:

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

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

    构造体:

    { ExplicitConstructorInvocationopt BlockStatementsopt }
    

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

    但也有解决办法。一种是创建一个工厂类,该类获取值并将其传递给派生类的构造函数

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

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

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

    我从不建议使用这种黑客手段。

    我有办法做到这一点

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

    虽然不可能直接执行,但可以尝试使用嵌套对象执行

    以你的例子来说:

    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定义为最后一个变量

    您是否试图预初始化字段
    a
    ?我认为您无法做到这一点。在类中执行的任何初始化(即使它在构造函数之外)都会在调用
    super
    后移动到每个构造函数。因此,超级构造函数总是在字段初始化之前运行。@fredsoverflow既然
    a
    只能在
    Derived
    中访问,那么在调用
    super()
    之前初始化它又有什么关系呢?在您提供的示例中,立即初始化它并没有什么不同(除非您从基本构造函数调用重写的方法,这开始让人觉得很可疑)如果重写方法依赖于子类构造函数执行的任何初始化,那么该方法将不会像预期的那样运行。“丑陋的黑客:直接用java字节码创建派生类,如下面的回答:+1-你是对的。哎呀。我在思考C++。尽管如此——我认为没有必要进行黑客攻击——请看我的答案。正如OP所问,这并不是在超级构造函数运行之前。可能是你能得到的最接近的了。@Keppil我在回答“超级构造函数在任何情况下都会运行”中提到过,这只是在派生类中初始化变量的一种技巧。在我看来,这是最好的解决方案。我唯一要改变的是,我将在
    派生的
    类中放置一个静态工厂方法。@mkilic:非常确定