Java/Kotlin中泛型的递归定义

2022-04-03  本文已影响0人  Wannay

1. 递归定义泛型

1.1 原始情况-问题由来

当我们定义了如下的关系的类时:

abstract class AbstractBuilder {
    private var id: Int? = null

    open fun setId(id: Int): AbstractBuilder {
        this.id = id
        return this
    }
}

open class MyBuilder1 : AbstractBuilder() {
    private var name: String? = null

    open fun setName(name: String): MyBuilder1 {
        this.name = name
        return this
    }
}

class MyBuilder2 : MyBuilder1() {
    private var age: Int? = null

    fun setAge(age: Int): MyBuilder2 {
        this.age = age
        return this
    }
}

我们想要依次完成setId/setName/setAge的链式调用,但是当我们完成了setId之后,发现完成不了setName和setAge了,因为setId返回的是AbstractBuilder类型,而不是它的子类类型。

MyBuilder2().setId(1)

如果我们想要完成链式调用,可以使用下面的代码

((MyBuilder2().setId(1) as MyBuilder2).setName("wanna") as MyBuilder2).setAge(18)

但是这个过程中,使用到了多次强转。但是我们明明已经知道this其实是个MyBuilder2类型了呀,为啥还要我强转啊!!!

原因在于,在父类当中根本无法知道子类的类型。这个很容易理解对吧,我怎么有哪些子类会存在?但是我们可以利用泛型去进行实现,我们通过泛型让子类告诉父类我是什么类型

1.2 使用泛型去实现让父类知道子类的类型

abstract class AbstractBuilder<B : AbstractBuilder<B>> {
    private var id: Int? = null

    open fun setId(id: Int): B {
        this.id = id
        return this as B
    }
}

open class MyBuilder1 : AbstractBuilder<MyBuilder1>() {
    private var name: String? = null

    open fun setName(name: String): MyBuilder1 {
        this.name = name
        return this
    }
}

class MyBuilder2 : MyBuilder1() {
    private var age: Int? = null

    fun setAge(age: Int): MyBuilder2 {
        this.age = age
        return this
    }
}

我们在AbstractBuilder当中定义了泛型B,而B又必须继承于AbstractBuilder<B>,在子类MyBuilder1当中,我们将泛型类型B设置为自己本身的类型,这样,在父类当中通过this as B,就可以拿到MyBuilder1类型了。

但是当我们调用完setId和setName之后,因为不管AbstractBuilder中的setId方法还是MyBuilder1中setName方法返回的还是MyBuilder1类型,并不是MyBuilder2类型的。

MyBuilder2().setId(1).setName("wanna")

我们继续改造

abstract class AbstractBuilder<B : AbstractBuilder<B>> {
    private var id: Int? = null

    open fun setId(id: Int): B {
        this.id = id
        return this as B
    }
}

open class MyBuilder1<B : MyBuilder1<B>> : AbstractBuilder<B>() {
    private var name: String? = null

    open fun setName(name: String): B {
        this.name = name
        return this as B
    }
}

class MyBuilder2 : MyBuilder1<MyBuilder2>() {
    private var age: Int? = null

    fun setAge(age: Int): MyBuilder2 {
        this.age = age
        return this
    }
}

我们让MyBuilder1也带上泛型B,并且约束泛型类型必须继承于MyBuilder1<B>,接着在MyBuilder2当中继承时,也带上MyBuilder2的泛型了。

接着,我们就可以完成我们的需求,依次完成setId/setName/setAge。

MyBuilder2().setId(1).setName("wanna").setAge(18)

不管是setId/setName/setAge,返回的类型都会根据我们传递的泛型类型去推断。也就是说,当我们使用MyBuilder2去创建对象时,setId/setName/setAge返回的都是MyBuilder2类型。

1.3 递归定义泛型的作用

我们想让父类A知道子类B的类型,我们完全可以在父类当中定义泛型H : A<H>,在子类B当中去定义一个泛型H : B<H>,如果B再来一个子类C,我们可以继续去在C当中去定义泛型H : B<H>。实际上,在很多语言当中,我们管父类中匹配子类的类型称为self,父类返回父类自身称为this,但是在Kotlin和Java当中,都是没有提供self的特性的。

有一个问题:为什么MyBuilder2当中继承父类的是<MyBuilder2>,而不是像之前说的,定义一个泛型H呢。因为MyBuilder2类本身就不是open的,也就是说这个类是final的,已经不允许子类去继承了,因此我们完全可以写死泛型类型。

还有个问题,我们如果想要构造一个MyBuilder1,去完成setId和setName呢?需要怎么做,因为该类MyBuilder1的泛型,是需要我们去定义的,我们需要传递一个MyBuilder1的泛型进去。

MyBuilder1<MyBuilder1>().setId(1).setName("wanna")

但是,这样使用是不是感觉没有MyBuilder2那样简洁呢?在Kotlin1.6之后,支持将泛型抹掉,直接使用如下的方式去进行构建:

MyBuilder1().setId(1).setName("wanna")

Kotlin会直接支持泛型的类型匹配,因为我们在MyBuilder1的泛型当中定义了泛型B : MyBuilder1<B>。因此当我们省略泛型时,Kotlin能够自动对泛型进行匹配,得到MyBuilder1的泛型类型,从而可以让我们省略泛型可以不写。但是,在Kotlin1.6之前是并不支持这样的泛型推断的。、、

实际上,Java当中完全同理

abstract class AbstractBuilder<B extends AbstractBuilder<B>> {

    private int id;

    public B setId(int id) {
        this.id = id;
        return (B) this;
    }
}

class MyBuilder1<B extends MyBuilder1<B>> extends AbstractBuilder<B> {

    private String name;

    public B setName(String name) {
        this.name = name;
        return (B) this;
    }
}

final class MyBuilder2 extends MyBuilder1<MyBuilder2> {

    private int age;

    public MyBuilder2 setAge(int age) {
        this.age = age;
        return this;
    }
}

完全能够推断出来self类型,MyBuilder1也支持不指定泛型去进行创建对象。

        new MyBuilder2().setId(1).setName("wanna").setAge(18);
        new MyBuilder1<>().setId(1).setName("wanna");

1.4 递归定义泛型的应用

在Netty中,定义了如下的类

AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel>

而AbstractBootStrap的子类有如下两个

ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>
Bootstrap extends AbstractBootstrap<Bootstrap, Channel>

在我们使用中,会使用如下的方式去创建ServerBootStrap,并启动Netty程序。

        NioEventLoopGroup bossGroup = new NioEventLoopGroup(bossLoops);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(workerLoops);
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(initializer)
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        ChannelFuture future = serverBootstrap.bind(port);

我们往往会用到option/childOptionhandler/childHandler等方法,但是带child的方法定义在子类,不带child的方法定义在父类,父类返回的确是子类类型的对象。

在父类当中,handler方法的实现如下,它返回的就是self,实际上就是基于递归定义泛型的方式去进行实现的在父类当中,返回子类类型的对象。

    public B handler(ChannelHandler handler) {
        this.handler = ObjectUtil.checkNotNull(handler, "handler");
        return self();
    }
    
    private B self() {
        return (B) this;
    }
上一篇下一篇

猜你喜欢

热点阅读