在调用超类的构造函数之前,有没有办法在Java中初始化子类的成员变量?

时间:2010-03-27 14:28:44

标签: java oop

我需要这个,因为超类中的构造函数正在调用在子类中重写的方法。该方法返回一个传递给子类构造函数的值。但必须在子类构造函数之前调用超类构造函数,因此我没有机会保存传入的值。

5 个答案:

答案 0 :(得分:17)

从超类构造函数调用重写方法根本不起作用 - 不要这样做。超类构造函数必须始终在子类之前完成。当超类构造函数正在执行时,有问题的对象是超类的(半初始化的)实例,而不是子类!因此,如果您尝试从构造函数调用任何重写的函数,它可能依赖的子类字段尚未初始化(正如您所观察到的那样)。这是课堂设计的一个基本事实,并没有解决方法。

Effective Java 2nd. Ed.(第4章,第17项)中所述:

  

一个班级必须遵守的限制允许   遗产。构造函数不能直接调用可覆盖的方法   或间接的。如果违反此规则,将导致程序失败。该   超类构造函数在子类构造函数之前运行,所以   子类中的重写方法将在子类之前调用   构造函数已运行。如果重写方法取决于任何初始化   由子类构造函数执行,该方法不会表现为   预期

如果您可以更改超类实现,请尝试将调用移出构造函数中的虚函数。实现此目的的一种方法是使用工厂方法:

class Super {
    public void init() { ... }
}

class Subclass extends Super {
    private Subclass() { super(); ... }
    public void init() { super.init(); ... }
    public static Subclass createInstance() {
        Subclass instance = new Subclass();
        instance.init();
        return instance;
    }
}

请注意Subclass的构造函数是私有的,以确保它只能通过createInstance()实例化,因此实例总是正确初始化。 OTOH这也阻止了进一步的子类化。但是,不建议继承子类化具体类 - 一个要进行子类化的类应该是抽象的(在这种情况下使用protected构造函数)。当然,任何进一步的子类还必须具有非公共构造函数和静态工厂方法,这些方法努力地调用init() ...

答案 1 :(得分:6)

您的问题由以下摘录汇总:

abstract class Base {
    Base() {
        dontdoit();
    }
    abstract void dontdoit();
}
public class Child extends Base {
    final int x;
    Child(int x) {
        this.x = x;
    }
    @Override void dontdoit() {
        System.out.println(x);
    }

    public static void main(String args[]) {
        new Child(42); // prints "0" instead of "42"!!!
    }
}

这是引自Josh Bloch和Neal Gafter的 Java Puzzlers:陷阱,陷阱和角落案例

  

只要构造函数调用已在其子类中重写的方法,就会出现问题。以这种方式调用的方法始终在实例初始化之前运行,此时其声明的字段仍具有其默认值。为了避免这个问题,永远不会直接或间接地从构造函数调用可覆盖的方法[EJ Item 15]。此禁止扩展到实例初始化器和伪辅助器readObjectclone的主体。 (这些方法称为伪结构,因为它们在不调用构造函数的情况下创建对象。)

简而言之:不要做它!

此外,本书提出了一个可能的解决方案(略微编辑一般性):

  

您可以通过在首次使用时懒惰地初始化字段来解决问题,而不是在创建实例时急切地。

答案 2 :(得分:1)

您没有提及是否可以修改超类的构造函数。如果可以,那么这是可行的。正如其他人所说的那样,这是不可取的,但是这里有一个人为的例子来做你正在寻找的东西 - 超类调用一个重写的方法,该方法使用传递给子类的构造函数的参数:

public abstract class Base {
        public Base(int value) {
                init(value);
                System.out.println(getValue());
        }

        public abstract void init(int value);
        public abstract int getValue();
}

public class Derived extends Base {
        private int value;

        public Derived(int value) {
                super(value);
        }
        public void init(int value) {
                this.value = value;
        }
        public int getValue() {
                return value;
        }
        public static void main(String[] argv) {
                new Derived(25);
        }
}

> java派生的 25

答案 3 :(得分:0)

我知道这个问题。这不可能。如果超级班不是你自己的,你没有其他选择,那么你可以做一些坏的黑客。但不建议这样做。

解决方案1:

使用具有同步的静态工厂。这看起来像(减少):

private static String savedValue;

public static Abc create(String value){
  synchronized(Abc.class){
    savedValue = value;
    Abc abc = new Abc();
    savedValue = null;
    return abc;
  }

private Abc(){
}

private value;

String getValue(){
  if( value == null ){
    value = savedValue;
  }
  return value;
}

解决方案2:

如果自己的构造函数已经使用标志运行,则在方法中进行区分。然后你需要复制具有实际值的超级构造函数的一些东西。

答案 4 :(得分:0)

如果这是一个问题,那么您的类层次结构中存在根本问题;超类不能知道任何特定的子类,因为可能有几个,它们甚至可能在运行时动态加载。这就是为什么构造函数中的任何super()调用都必须在第一行。

作为一般规则,不要在构造函数中调用任何公共方法。