kotlin小知识之泛型和委托(代码片段)

z啵唧啵唧 z啵唧啵唧     2022-12-21     795

关键词:

文章目录

泛型和委托

泛型的基本用法

  • Kotlin当中的泛型机制和Java当中的泛型机制还是有异同的
  • 所谓泛型就是说在一般的编程模式下面,我们需要给一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码将会拥有更好的扩展性.
  • 比如List是一个可以存放数据的列表,但是List并灭有限制我们只能存放整形数据或者字符串数据,因为他没有指定一个具体的类型,而是使用泛型来实现,就是因为如此我们才可以使用List, List…
  • 泛型主要有两种定义方式,一种定义泛型类,一种是定义泛型方法,使用的语法结构都是,当然括号当中的T并不是固定要求的,事实上你写任何英文字母都可以,但是通常情况下,T是一种约定俗成的泛型写法.
  • 如果我们要定义一个泛型类,就可以这样写
class MyClass<T> 
    fun method(param: T) : T 
        return param
    

  • 此时MyClass类就是一个泛型类,MyClass中的方法允许使用T类型的参数和返回值
  • 我们在调用MyClass类和method()方法的时候,就可以将泛型指定成具体的类型,如下所示
val myClass = MyClass<Int>()
val res = myClass.method(123)
  • 这里我们将MyClass类的泛型定义成为了Int类型,于是method()就可以接收一个Int类型的参数了,并且返回值也变成了Int类型
  • 但是如若我们不想定义一个泛型类,只想定义一个泛型方法,可以这样做
  • 只需要将定义泛型的语句结构写在方法上面就可以了,如下所示
class MyClass 
    fun <T> method(param: T) : T 
        return param
    

  • 此时的调用方式也需要做如下的调整
val myClass = MyClass()
val res = myClass.method<Int>(123)
  • 可以看到,现在是在调用method()方法的时候指定泛型数据
  • 另外Kotlin还拥有非常出色的类型推到机制,例如我们传入了一个Int类型的参数,它能够自动推导出泛型的类型是Int类型,于是我们在调用的时候还可以使用这种简便写法
val myClass = MyClass()
//传入Int类型的参数,自动能推导出泛型的类型为Int
val res = myClass.method(123)
  • Kotlin还允许我们对泛型的类型进行限制.比如我们不想将method()方法的泛型指定成任意类型,我们可以通过指定上界的方式来对泛型进行约束,比如在这个地方将method()方法的泛型指定成为Number类型,如下所示
class MyClass 
    fun <T: Number> method(param: T) : T 
        return param
    

  • 这种方法就表明,我们只能将nethod()方法的泛型指定成为数字类型,比如:Int, Float,Double等
  • 另外在默认的情况下,所有的泛型都是可以指定为可空类型的,这是因为在不手动指定上界的情况下,泛型上街默认是Any?
  • 而如果想要泛型的类型不可为空,只需要将泛型的上界手动指定成Any就可以了
  • 之前给StringBuilder类编写过一个build函数,代码如下
fun StringBuilder.build(block: StringBuilder.() -> Unit) : StringBuilder 
    block()
    return this

  • 这个函数的作用和apply函数基本是一样的,但是build函数只能在StringBuilder类中生效,而apply函数是可以作用在所有类上面的,使用泛型的知识可以对build函数进行扩展,让他实现和apply函数完全一样的功能
  • 其实并不复杂,只需要使用将build函数定义成为泛型函数,再将原来所有强制指定StringBuilder的地方替换成为T就可以了
fun <T>.build(block: T.() -> Unit) : T 
    block()
    return this

  • 这样编写出来之后,就可以像apply函数一样去使用build函数了

类委托和委托属性

  • 委托是一种设计模式,它的基本思想是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去进行处理
  • 在Java当中没有对于委托实现语言层面的实现,而在C#等语言当中就对委托进行了原生的支持
  • 在Kotlin当中也是支持委托的,并且将委托功能分为了两种:类委托和委托属性

类委托

  • 类委托的核心思想是:将一个类的具体实现委托给另外一个类去完成
  • 我们曾经使用过Set这种数据类型,他和List比较类似,只是它所存储的数据都是无序的,并且不能重复存放数据
  • Set是一个接口,如果要使用它的话,需要他的具体实现,比如HashSet,而借助于委托模式,我们可以轻松实现一个自己的实现类
  • 定义一个MySet,并让他实现Set接口,代码如下所示
class MySet<T>(val helperSet: HashSet<T>) : Set<T> 
    override val size: Int
        get() = helperSet.size
    override fun contains(element: T) = helperSet.contains(element)
    override fun contanis(elements: Collection<T>) = helperSet.containsAll(elements)
    override fun isEmpty() = helperSet.isEmpty()
    override fun iterator() = helperSet.iterator()

  • 可以看到,MySet的构造函数中接受了一个HashSet参数,这就相当于一个辅助对象,然后在Set接口所有的方法实现中,我们都没有进行自己的实现,而是调用了辅助对象的方法进行实现,这个就是一种委托模式.
  • 那么这种写法的好处是什么呢?加入我们让大部分的方法实现调用辅助对象当中的方法,少部分的方法实现由自己重写,甚至加入一些自己独有的方法,那么MySet就会成为一个全新的数据结构类,这就是委托模式的意义所在.
  • 但是这种写法也有自己的弊端,如果接口中待实现的方法比较少比较好,要是有几十甚至几百个方法的话,每个都去这样调用辅助对象中相应方法实现,呢就要写哭了.
  • 在Kotlin当中针对这个问题采用了类委托的方式进行解决.
  • Kotlin当中委托使用的关键字是by,我们只需要在接口声明的后面使用by关键字,再接上受委托的辅助对象,就可以免去之前所写的一大堆模板式的代码了,如下所示:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helpSet 
    

  • 这段代码的作用和上述代码的实现效果是一模一样的,但是借助了类委托之后,代码明显简化了很多
  • 另外,如果我们想要对某个方法重新进行实现,只需要单独重写那一个方法就行了,其他的方法仍然可以享受类委托所带来的便利,如下所示
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet 
    fun helloWorld() = println("Hello World")
    override fun isEmpty() = false

  • 在这里我们新增了一个helloWorld()方法,并且重写了isEmpty()方法,让他永远返回false,现在我们的MySet就成为了一个全新的数据结构类

委托属性

  • 委托属性的核心思想是将一个属性(字段)的具体实现委托给另外一个类去完成
  • 委托属性的语法结构
class MyClass 
    var p by Delegate()

  • 可以看到这里使用了by关键字连接左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给Delegate类去实现.
  • 当调用p属性的时候,会自动调用Delegate类的getValue()方法,当给p属性赋值的时候,会自动调用Delegate类的setValue()方法
  • 因此我们还需要对Delegte类进行具体实现才行
class Delegate 
    var propValue: Any? = null
    operator fun getValue(myClass: MyClass, prop: KProperty<*>) : Any? 
        return propValue
    
    operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) 
        propValue = value
        

  • 这是一种标准的代码实现模板,再Delegate类中我们必须实现getValue()和setValue()方法,并且都要使用operator关键字进行声明
  • getValue()方法要接收两个参数,第一个参数用于声明该Delegate类的委托功能再什么类当中进行使用,这里写成MyClass表示仅在MyClass类当中进行使用
  • 第二个参数KProperty<*>是Kotlin当中的一个属性操作类,可以用于获取各种属性相关的值
  • <*>这种泛型类的写法表示你不知道或者不关心泛型的具体类型,只是为了通过语法编译而已,有点类似于Java当中的<?>写法,只是为了t通过语法编译而已,至于返回值可以声明成任何类型,根据具体的实现逻辑去写就行
  • setValue()方法也是类似的,只不过他要接收三个参数,前两个参数和getValue()方法是相同的,最后一个参数表示具体给委托属性的值,这个参数的类型必须和getValue()方法返回值的类型保持一致.
  • 整个委托的工作流程是这样实现的,现在当我们给MyClass的p属性赋值的时候,就会调用Delegate类的setValue()方法,当获取MyClass中的p属性的值的时候,就会调用Delegate类的getValue()方法.
  • 还有一种情况可以不用在Delegate类中定义setValue()方法,那就是当我们给MyClass的p属性声明为val关键字的时候,因为p属性使用val关键字进行声明,那么就意味着p属性是无法在初始化之后再进行重新赋值的,因此也没有必要实现setValue()方法,只需要实现getValue()方法就可以了.

实现一个自己的lazy函数

  • lazy是kotlin当中的一种懒加载技术.把想要延迟执行的代码放到by lazy代码块当中,这样代码块在一开始就不会执行,只有当变量被首次调用的时候,代码块中的代码才会执行.
  • 事实上by laze …只有by才是Kotlin当中的关键字,lazy在这里只是一个高阶函数
  • 再lazy函数中会创建并且返回一个Delegate对象,当我们调用p属性的时候,其实调用Delegate对象的getValue()方法,然后getValue()方法中又会调用lazy函数传入的Lambda表达式,这样表达式中的代码就得到了执行,并且调用p属性后得到的值就是Lambda表达式中最后一行代码返回的值.
  • 新建一个Later.kt文件
import kotlin.reflect.KProperty

/**
 * @Description: 定义一个Later类,并将他指定成为泛型类型
 * Later的构造函数中接收一个函数类型参数,这个函数类型参数不接受任何参数
 * 并且返回值类型就是Later类指定的泛型类型
 * @Author zb~
 * @Date 2022/12/20 22:38
 */
class Later<T>(val block: () -> T) 
    var value: Any? = null
    operator fun getValue(any: Any?, prop: KProperty<*>): T 
        if (value == null) 
            value = block()
        
        return value as T
    

  • 这里将getValue()方法的第一个参数指定成为了Any?类型,表示我们希望Later的委托功能在所有类中都可以进行使用,然后使用一个value变量对值进行缓存,如果value为空就调用构造函数中传入的函数类型参数去获取值,否则就直接进行返回.
  • 由于懒加载技术是不会对属性进行赋值的,因此在这里我们就不用实现setValue()方法了
  • 代码写到这里,委托属性的功能就已经完成了,我们已经可以进行使用了,但是为了让他的用法更加类似于lazy函数,最好再定义一个顶层函数,这个函数直接写在Later.kt文件中就可以了,但是要定义在Later类的外面,因为不定义在任何类中的函数才是顶层函数,代码如下所示
/**
 * 定义一个泛型函数,作用是创建Later实例,并将接收函数类型参数传给Later类的构造函数
 * @param block Function0<T> 接收一个函数类型的参数
 * @return Later<T>
 */
fun <T> later(block: () -> T) = Later(block)
  • 现在我们自己编写的later懒加载函数就已经完成了,可以用它来直接代替lazy函数.

java基础知识之泛型简单介绍(代码片段)

1、什么是java泛型?java泛型:英文名称是generics,泛型是jdk5引入的一个新特性,java泛型的本质就是参数化类型,就是所有的操作数据类型被指定为一个参数。对参数化类型进行操作的实体(例如类、接口... 查看详情

java基础知识之泛型简单介绍(代码片段)

1、什么是java泛型?java泛型:英文名称是generics,泛型是jdk5引入的一个新特性,java泛型的本质就是参数化类型,就是所有的操作数据类型被指定为一个参数。对参数化类型进行操作的实体(例如类、接口... 查看详情

尝鲜dart2.7最新语法之泛型强化:声明处型变(代码片段)

...,强烈建议先把该专栏的第11篇内容学习下。如果你有过Kotlin、C#、Java的开发经验,对泛型协变、逆变、不变或许不陌生。如果你仅仅熟悉目前Dart的泛型,也许你会有点陌生,因为在目前Dart版本中默认都是泛型协变的。默认都... 查看详情

clr类型设计之泛型

      在上一篇文章中,介绍了什么是泛型,以及泛型和非泛型的区别,这篇文章主要讲一些泛型的高级用法,泛型方法,泛型泛型接口和泛型委托,协变和逆变泛型类型参数和约束性,泛型的高级用法在平... 查看详情

java基础语法java的泛型和包装类(代码片段)

前言:本章主要是为了后面学习集合框架所做的知识补充。补充了泛型以及包装类两个知识,但是该章泛型的讲解不够全面,主要是为了集合框架学习做铺垫。文章目录1.预备知识-泛型(Generic)1.1泛型的引入1... 查看详情

java基础之泛型(代码片段)

泛型genericity格式:<自定义泛型无意义大写英文字母占位符>例:<T>Type<E>Element<K>Key<V>value泛型可以在方法,类,接口中使用泛型在方法中使用使用静态方法的泛型格式:publicstatic<T>返回值类型[自定义... 查看详情

c#语言中泛型和委托的关系是啥,func<int>是泛型还是委托?

C#语言中泛型和委托的关系是什么,Func是泛型还是委托?参考技术AFunc既是泛型也是委托。表示一个没有参数,有int返回值的函数。 查看详情

java遗珠之泛型的作用(代码片段)

泛型总共有三个作用编译时进行更强大的类型检查编译时错误比运行时错误更好发现和处理消除类型转换Listlist=newArrayList();list.add("hello");Strings=(String)list.get(0);使用泛型之后去掉转换List<String>list=newArrayList<Str... 查看详情

java遗珠之泛型七大限制(代码片段)

不能使用原始类型实例化泛型类型参数化类型如下:publicclassOrderedPair<K,V>implementsPair<K,V>privateKkey;privateVvalue;publicOrderedPair(Kkey,Vvalue)this.key=key;this.value=value;publicKgetKey()ret 查看详情

clr类型设计之泛型

      在讨论泛型之前,我们先讨论一下在没有泛型的世界里,如果我们想要创建一个独立于被包含类型的类和方法,我们需要定义objece类型,但是使用object就要面对装箱和拆箱的操作,装箱和拆箱会很损耗... 查看详情

java遗珠之泛型多边界(代码片段)

泛型的类型参数可以有多个边界<TextendsB1&B2&B3>当其中一个边界是class的时候需要写在前面。classA/*...*/interfaceB/*...*/interfaceC/*...*/publicclassD<TextendsA&B&C>/*...*/写在后面的话会有编译错误classD<TextendsB& 查看详情

java遗珠之泛型继承(代码片段)

当给定两个具体的类型A和B,MyClass<A>和MyClass<B>没有任何关系,不管A和B是什么关系。你可以通过扩展泛型类或者实现泛型接口的interfacePayloadList<E,P>extendsList<E>voidsetPayload(intindex,Pval);//...以下参数化的Payloa... 查看详情

java基础知识(java之泛型)

   什么是泛型?为什么要使用泛型?泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的... 查看详情

typescript入门手册之泛型(代码片段)

【Typescript入门手册】之泛型🚀【TypeScript入门手册】记录了出场率较高的Ts概念,旨在帮助大家了解并熟悉Ts🎉本系列会持续更新并更正,重点关照大家感兴趣的点,欢迎同学留言交流,在进阶之路上,... 查看详情

typescript入门手册之泛型(代码片段)

【Typescript入门手册】之泛型🚀【TypeScript入门手册】记录了出场率较高的Ts概念,旨在帮助大家了解并熟悉Ts🎉本系列会持续更新并更正,重点关照大家感兴趣的点,欢迎同学留言交流,在进阶之路上,... 查看详情

第三节:java数据结构预备知识之泛型

上一节内容讲时间和空间复杂度:(王道408考研数据结构)第一章绪论-第二节2:算法的时间复杂度和空间复杂度注意:泛型是Java语法中比较难掌握的部分,所以了解即可文章目录一:什么是泛型二:泛型语法(1)定义·(2)使用... 查看详情

java语法糖之泛型与类型擦除(代码片段)

1泛型与类型擦除泛型,JDK1.5新特性,本质是参数化类型(ParametersizedType)的应用,即所操作的数据类型被指定为一个参数。这种参数类型可用在:类接口方法的创建中,分别称为:泛型类泛型接口泛型方法在Java还... 查看详情

java遗珠之泛型类型擦除(代码片段)

擦除规则泛型的作用之前已经介绍过了只是用于编译之前更为严格的类型检查,其他的一些特性也都是编译之前的,在编译之后泛型是会被擦除掉的。类型擦除所做的事情如下:如果是无界限的则会把类型参数替换成... 查看详情