可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have an abstract class (Parent
) which has an abstract method (doSetup
), and a member method which calls the doSetup
method. What I need is the child class (which implements Parent
) at construction should automatically call the doSetup
method, regardless of however many constructors the child class might have. Is there a Java mechanism or a design pattern which could help me solve this?
public abstract class Parent {
abstract protected void sayHi();
protected void doSetup() {
sayHi();
}
}
public class Child1 extends Parent {
@Override
protected void sayHi() {
System.out.println("hi");
}
public Child1() {
// Construction needs to automatically include exec of doSetup()
}
public Child1(String string) {
// Construction needs to automatically include exec of doSetup()
System.out.println("another constructor");
}
}
回答1:
This is one way to implement common construction code:
Parent.java
public abstract class Parent {
public Parent() {
this("Default Value Goes Here");
}
// Funneling everything through this main constructor.
public Parent(String value) {
this.doSetup(value);
}
// I've made this method private, as it shouldn't really
// be accessed from sub classes, but if you require that, then
// mark this method as protected & final instead.
private void doSetup(String value) {
System.out.println(value);
}
}
Child.java
public class Child extends Parent {
// Deliberately not implementing constructors here,
// but if I did, the first call would be to a super()
// constructor to retain parent's construction functionality.
}
MainApp.java
public class MainApp {
public static void main(String[] args) {
Child child = new Child();
}
}
Run the MainApp above and you will see that the default value constructor is run and it will output "Default Value Goes Here", as it is enforced by the parent's constructor.
回答2:
A good IDE probably will warn against using overridable methods in the constructor.
The reason can be demonstrated with the following code that has maybe surprising results.
class Base {
Base() {
init();
}
protected void init() {
}
}
class Child extends base {
String a = "a";
String b;
String c = "c";
String d;
public Child() {
// 1. Fields are nulled
// 2. super() called
// 2.1. init() called
// 3. Field initialisations done (a, c)
// 4. Rest of constructor:
System.out.printf("EndConstr a: %s, b: %s, c: %s%n", a, b, c);
}
@Overridable
protected void init() {
System.out.printf("Init a: %s, b: %s, c: %s%n", a, b, c);
c = "cc";
d = "dd";
}
}
A solution to control behavior would be to offer one final non-overridable method that calls an overridable protected method in a specified way:
class Base {
public final void f() {
X x = ...;
onF(x);
}
protected /*abstract*/ void onF(X x) {
}
}
class Child extends Base {
@Overridable
protected void onF(X x) {
...
}
}
回答3:
Why not include doSetup()
in the parent constructor? To avoid calling an overrideable method from the constructor, you could alter your pattern a bit:
public abstract class Parent {
final String greeting;
public Parent(String greeting) {
this.greeting = greeting;
doSetup();
}
final void doSetup() {
System.out.println(greeting);
}
}
Then each of your subclasses' constructors will need to explicitly call the super constructor, for instance:
public class Child1 extends Parent {
private static String default_greeting = "hi";
public Child1() {
super(default_greeting); // prints "hi"
}
public Child1(String string) {
super(string); // print a different greeting
}
}
回答4:
As others have stated, you shouldn't call an overridable method from within the constructor of a super class. This is to avoid this
to escape and eventually produce hard-to-debug race conditions or to just avoid NullPointerException
s to be thrown while accessing a yet uninitialized field from within overriden methods.
Now, with regard to the question, if you want to run common code to all contructors, what you need is an initializer block:
public abstract class Parent {
{
// this is an initializer block that is inherited
// by all subclasses and runs for every constructor
// in the hierarchy
this.doSetup();
}
protected final void sayHi() { // final to avoid this to escape
System.out.println("hi");
}
protected final void doSetup() { // final to avoid this to escape
sayHi();
}
}
public class Child1 extends Parent {
public Child1() {
// initilizer block automatically called
}
public Child1(String string) {
// initilizer block automatically called
System.out.println("another constructor");
}
}
Running this test:
new Child1();
new Child1("something");
Produces the following output:
hi
hi
another constructor