Можно ли вызвать абстрактный метод из конструктора в Java?
Предположим, что у меня есть абстрактный базовый класс, реализующий интерфейс Runnable.
public abstract class Base implements Runnable {
protected int param;
public Base(final int param) {
System.out.println("Base constructor");
this.param = param;
// I'm using this param here
new Thread(this).start();
System.out.println("Derivative thread created with param " + param);
}
@Override
abstract public void run();
}
И вот один из нескольких производных классов.
public class Derivative extends Base {
public Derivative(final int param) {
super(param);
}
@Override
public void run() {
System.out.println("Derivative is running with param " + param);
}
public static void main(String[] args) {
Derivative thread = new Derivative(1);
}
}
Дело в том, что я хочу, чтобы мой базовый класс делал некоторые общие вещи, а не копировал его каждый раз.
На самом деле, он работает нормально, выход всегда один и тот же:
Базовый конструктор
Производный поток, созданный с параметром 1
Производный работает с параметром 1
Но безопасно ли в JAVA запустить поток, вызывающий абстрактный метод в конструкторе? Потому что, в С++ и С#, это небезопасно в большинстве случаев, насколько я знаю.
Спасибо!
Ответы
Ответ 1
Этот код демонстрирует, почему вы никогда не должны вызывать абстрактный конструктор или любой другой переопределяемый метод из конструктора:
abstract class Super {
Super() {
doSubStuff();
}
abstract void doSubStuff();
}
class Sub extends Super {
String s = "Hello world";
void doSubStuff() {
System.out.println(s);
}
}
public static void main(String[] args) {
new Sub();
}
При запуске это печатает null
. Это означает, что только "безопасные" методы, которые есть в конструкторе, являются частными и/или конечными.
С другой стороны, ваш код фактически не вызывает абстрактный метод из конструктора. Вместо этого вы передаете неинициализированный объект в другой поток для обработки, что хуже, поскольку начинающийся поток может иметь приоритет и выполнять до того, как ваш Base
завершит инициализацию.
Ответ 2
Это очень плохая практика для вызова абстрактного метода из конструктора. Методы, вызываемые из конструкторов, всегда должны быть закрытыми или окончательными, чтобы предотвратить переопределение.
Смотрите эту ссылку на вопрос здесь
Ответ 3
Не очень хорошая идея, так как при вызове run() объект Derivative может не быть инициализирован. Если run() зависит от любого состояния в производном, он может выйти из строя.
В вашем простом случае это работает. Но тогда нет никакого смысла для подкласса. Вы можете просто
public Base(final int param, Runnable action) {
new Thread(action).start();
Ответ 4
Передача this
из конструктора называется "позволяя this
выйти из конструктора" и может привести к некоторым особенно неприятным и странным ошибкам, поскольку объект может находиться в несогласованном состоянии.
Это особенно относится к случаю, когда this
передается в другой поток, как в этом примере. Из-за того, что JVMs имеет право переупорядочивать утверждения в потоке, вы можете получить поведение undefined поведение/состояние.