对于链式调用,只需要在某些操作方法中返回this
即可:
class A {
protected String name;
public A setName(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
}
如上,使用时:
String name = new A().setName("nameA").getName();
然而,父类定义的链式方法,并不能被子类有效继承:
class B extends A {
protected Integer age;
public B setAge(Integer age) {
this.age = age;
return this;
}
public Integer getAge() {
return age;
}
}
使用时:
// 编译器报错。
String name = new B().setName("nameB").setAge(18).getName();
此时编译器会报错。这是因为调用setName()
时,尽管此时实际的this
是B
类型,但返回的类型是A
,而A
并没有setAge()
方法。
因此,对其进行调整:
// 可以运行。
String name = new B().setAge(18).setName("nameA").getName();
这样就能顺利运行。因为调用setAge()
返回的类型是B
,而B
继承了A
的方法,因此可以继续调用setName()
。setName()
之后调用的getName()
也是A
的方法,因此虽然setName()
返回的类型是A
,却并不会影响到getName()
的调用。
但这样就存在一个问题:在链式调用中,必须先调用子类的方法,然后再调用父类的方法。这种限制是非常大的,对使用者非常不友好。
一种最简单的解决方案就是重载:
class B extends A {
protected Integer age;
public B setAge(Integer age) {
this.age = age;
return this;
}
public Integer getAge() {
return age;
}
@Override
public B setName(String name) {
this.name = name;
return this;
}
}
在子类B
中,对父类需要返回this
的方法进行重载,令其返回B
类型。这样即可解决前面的问题:
// 可以运行。
String name = new B().setName("nameB").setAge(18).getName();
但该方案比较繁琐,有多少返回this
的方法就得重载多少次。并且重载只对本类有效,若本类还有子类,那么只能继续重载。
因此,适用于重载来解决链式调用继承问题的场景为:
this
的方法很少。既然问题是父类返回this
时的类型导致的,那么令其直接返回子类的类型不就可以解决了么。
基于此思路,可以使用泛型方式:
class A<T> {
protected String name;
public T setName(String name) {
this.name = name;
return (T)this;
}
public String getName() {
return name;
}
}
class B extends A<B> {
protected Integer age;
public B setAge(Integer age) {
this.age = age;
return this;
}
public Integer getAge() {
return age;
}
}
为父类A
添加泛型,在子类B
定义时将自身作为泛型参数传入。
// 可以运行。
String name = new B().setName("nameB").setAge(18).getName();
该方法不需要额外多写重载代码,更加简洁。
但此时若直接使用A
类,依然要传入一个泛型参数:
// 编译器报错。
String name = new A().setName("nameA").getName();
// 可以运行。
String name = new A<A>().setName("nameA").getName();
这是因为若不传入泛型参数,则会默认取Object
类型。故而此时setName()
返回的类型是Object
。由于Object
并没有getName()
方法,因此会报错。
基于此,考虑为A传入一个派生于A
的泛型,这样即使取默认值,也是A
的派生类:
class A<T extends A> {
...
}
// 可以运行。
String name = new A().setName("nameA").getName();
这样即可较好地解决上述所有问题。
对于只存在一级派生关系的类,可直接使用该方案。
上述方案只适用于一级派生类。若派生层级有多级,依然是存在问题的:对B
的子类C
,setName()
返回的类型是B
,而非C
。于是又遇到了前面的问题。于是再次进行改进:
class A<T extends A> {
...
}
class B<E extends B> extends A<E> {
...
}
class C<K extends C> extends B<K> {
...
public K setNumber(Integer number) {
this.number = number;
return (K)this;
}
}
这样即可解决多级派生的链式调用继承问题。但要注意的是子类必须给出泛型参数:
// 以下代码编译器报错。
String nameB = new B().setName("nameB").setAge(18).getName();
String nameC = new C().setName("nameC").setNumber(99).setAge(18).getName();
// 以下代码均可顺利运行。
String nameA = new A().setName("nameA").getName();
String nameB = new B<B>().setName("nameB").setAge(18).getName();
String nameC = new C<C>().setName("nameC").setNumber(99).setAge(18).getName();
链式调用是为了简洁与易懂。但java本身对链式调用的支持并不是很友好。考虑到实际情况,建议使用链式调用仅局限在父子层级中,不要涉及更深的层级。