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/childOption
、handler/childHandler
等方法,但是带child的方法定义在子类,不带child的方法定义在父类,父类返回的确是子类类型的对象。
在父类当中,handler方法的实现如下,它返回的就是self,实际上就是基于递归定义泛型的方式去进行实现的在父类当中,返回子类类型的对象。
public B handler(ChannelHandler handler) {
this.handler = ObjectUtil.checkNotNull(handler, "handler");
return self();
}
private B self() {
return (B) this;
}