2023-11-08 16:16

Java中的lambda链式调用

wanmatea

JavaEE

(431)

(0)

收藏

常规链式调用

对于链式调用,只需要在某些操作方法中返回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条评论

点击登录参与评论