Есть ли способ инициализировать переменные-члены подкласса в Java до вызова конструктора суперкласса?
Мне это нужно, потому что конструктор в суперклассе вызывает метод, который переопределяется в подклассе. Метод возвращает значение, которое передается конструктору подкласса. Но конструктор суперкласса должен вызываться перед конструктором подкласса, поэтому у меня нет возможности сохранить переданное значение.
5 ответов
вызов переопределенного метода из конструктора суперкласса просто не сработает - не делай этого. конструктор суперкласса всегда должен завершаться до завершения подкласса. В то время как конструктор суперкласса выполняется, объект, о котором идет речь, является (наполовину инициализированным) экземпляром суперкласса, а не подкласса! Поэтому, если вы попытаетесь вызвать любую переопределенную функцию из конструктора, поля подкласса, от которых она может зависеть, еще не инициализированы (как и у вас наблюдаемый.) Это фундаментальный факт дизайна класса, и нет обходного пути.
как поясняется в эффективная Java 2nd. Эд. (Глава 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
is private, чтобы гарантировать, что он может быть создан только через createInstance()
, таким образом, экземпляры всегда инициализируются правильно. OTOH это также предотвращает дальнейший подкласс. Однако подклассы конкретного класса в любом случае не рекомендуются - класс, предназначенный для подкласса, должен быть абстрактным (с protected
конструктор в данном случае). И, конечно, любые дальнейшие подклассы также должны иметь непубличные конструкторы и статические заводские методы, которые старательно называют init()
...
ваш вопрос резюмируется следующим фрагментом:
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"!!!
}
}
Это цитата из Джоша Блоха и Нила Гафтера Java Puzzlers: ловушки, подводные камни и угловые случаи:
проблема, когда конструктор вызывает метод, который был переопределен в подклассе. Метод, вызываемый таким образом, всегда выполняется до инициализации экземпляра, когда его объявленные поля все еще имеют значения по умолчанию. Чтобы избежать этой проблемы, никогда не вызывайте переопределяемые методы из конструкторов, прямо или косвенно [EJ пункт 15]. Этот запрет распространяется на инициализаторы экземпляров и тела псевдоконструкторовreadObject
иclone
. (Эти методы называются псевдоконструкторами, поскольку они создают объекты без вызова конструктора.)
короче: не делайте этого!
кроме того, книга предлагает возможное решение (немного отредактированы для общность):
вы можете исправить проблему, инициализируя поле лениво, когда оно впервые используется, а не с нетерпением, когда создается экземпляр.
вы не упомянули, можете ли вы вообще изменить конструктор суперкласса. Если вы можете, то это выполнимо. Как говорили другие, это не рекомендуется, но вот надуманный пример, который делает то, что вы ищете-суперкласс вызывает переопределенный метод, который использует аргумент, который был передан конструктору подкласса:
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
Я знаю проблему. Это невозможно. Если супер класс не является вашей собственностью и у вас нет других вариантов, то вы можете сделать некоторые плохие писаки. Но это не рекомендуется.
Решение 1:
используйте статическую фабрику с синхронизированным. Это может выглядеть так (redudced):
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:
сделайте дифференцирование в своем методе, если собственный конструктор уже был запущен с флагом. Затем вам нужно дублировать некоторые вещи супер конструктора с реальным значением.
Если это проблема, у вас есть фундаментальная проблема в иерархии классов; суперклассы не могут знать о каком-либо конкретном подклассе, потому что их может быть несколько, они могут даже загружаться динамически во время выполнения. Вот почему любые вызовы super () в конструкторе должны быть в первой строке.
Как правило, не вызывайте никаких открытых методов в конструкторе.