Java/Kotlin中泛型的递归定义

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;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容