草庐IT

Java/Kotlin中泛型的递归定义

Wannay 2023-03-28 原文

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;
    }

有关Java/Kotlin中泛型的递归定义的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  3. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  4. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  5. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  6. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  7. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  8. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  9. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  10. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

随机推荐