常规链式调用
对于链式调用,只需要在某些操作方法中返回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本身对链式调用的支持并不是很友好。考虑到实际情况,建议使用链式调用仅局限在父子层级中,不要涉及更深的层级。
0条评论
点击登录参与评论