ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[转载] Kotlin 泛型

2022-02-02 16:04:37  阅读:186  来源: 互联网

标签:java val Kotlin List 类型 kotlin 泛型 转载


这是一篇转载文章,出处信息如下:

项目 内容
原作者 Jasper
出处 https://jasper1024.com/jasper/op635cser93/
许可 CC-BY-NC-SA 许可协议
修改状况 未做任何修改

导语

泛型一直用的很多了,在使用中也有不少新的体会,故重新总结到此.

基本概念

参照维基有两种定义

  • 在程序编码中一些包含类型参数的类型,也就是说泛型的参数只可以代表类,不能代表个别对象.(这是当今较常见的定义)
  • 在程序编码中一些包含参数的类,其参数可以代表类或对象等等.(现在人们大多把这称作模板)

kotlin/java 中是前一种,即泛型只代表类.kotlin 的泛型在 jvm 上实现与 java 泛型有很大联系,故下文会将两者类似的语法放在一块对比.

基础

使用泛型的地方无非是类/方法/接口.

泛型类,声明泛型类与 java 没差别,使用时需要带上具体的类型.

class Box<T>(t: T) {
    var value = t
}
val box: Box<Int> = Box<Int>(1)

泛型方法,与 java 类似的可以限制 T 的上下界.多重约束时使用 where,在 java 中则是 <T extend a & b>.

fun <T> boxIn(value: T) = Box(value)
fun <T : a> boxIn(value: T) = Box(value)
fun <T> boxIn(value: T)
        where T : a,
              T : b
    { Box(value) }

泛型接口类似,不再重复.

型变

java 泛型使用时有一种限制是,子类集合的类型,不是父类集合的子类,说人话就是 IntegerNumber 的子类,但是List<Integer>List<Number> 没有继承关系.但有时我们又需要在 子类集合 与 父类集合 之间建立联系,而这个关联的操作就是型变.

一个例子: 我有一堆传感器采集数据,我想有一个全局的数组存放这些数据,结果俩不同牌子都是采集温度的传感器,一个传回来是 List<Integer> 一个是 List<Long>.kt 中没有隐式的转换,我不想太麻烦,就声明了一个全局的 List<Number> 实例,但是 List<Integer> 是无法直接添加到 List<Number> 实例的,这里就需要用到型变了.

型变有两种: 协变 和 逆变,又分 使用时型变 和 声明时型变.

协变

上文例子就算协变.即子类集合类型是父类集合类型的子类.List<Integer> 可以作为 List<Number> 的子类.

java/kotlin 的协变有一个限制是,只读不可写.即我可以使用 List<Integer> 添加到 List<Number> 中,却不可以使用专属于 List<Integer> 的方法和属性.

声明: kotlin 的 out 更加易懂一点,即传入的实例只作为输出,只读.

List<? extend Number> //(java) 这里的 extend 与 T extend xx,含义不同,千万注意.
List<out Number> //(kotlin)

java 中协变只能在使用时声明协变,即 ? extend 后面需要是一个具体的类型(只能如上面的示例),不能是泛型的符号 T.但是 kotlin 中 out 后面可以接泛型类型,即 kt 允许声明时协变.

interface Test<out T> {...}
interface Test<? extends T> { ... } //java 不允许

逆变

与协变相反的是逆变.List<? super Integer> 可以传入 List<Number>.这样 List<Number>List<? super Integer> 的子类,但是 Number 却是 Integer 的父类.这样的转换称为逆变.

与协变类似的限制是,例如声明 List<? super Integer>,这样可以传入 List<Number> 类型,传入的实例只能写不能读取任何属性.

声明: kt 的in更简单一点,即传入的实例只写不读.

List<? super Integer> //java ,这里super 与 T super xx 含义不同.
List<in Number>  //kotlin

同样的,kt 中支持声明时逆变,java 不支持.

fun<in T> test(){} //可以
public <? super T> void test(){...}//java 中不允许

星投影

java 中可以使用单个 ? ,相当于 ? extend Object.

kotlin 中类似的写法是 * ,相当于 out Any,但如果你的类型声明中已经有了 out 或 in,这个限制在变量声明时依然存在.

interface Test<out T:Number>{...}
var tmp : Text<*> = ... //这里的 * 仅被视为 out Number

“真”泛型

kotlin 和 java 的泛型都是会执行编译期的类型擦除,即除了型变的情况外,泛型实例在内存中是 object 类型而不是代码中我们传入的类型.

因为类型擦除的特性,相对于能在运行时拿到具体类型的真泛型而言,java 的泛型有时被称为”假”泛型.那 kotlin 在 android/jvm 的实现必须与 java 兼容,因此 kotlin 的泛型也会有类型擦除,但是 kt 还留了一手,通过声明 reified 就能实现”真”泛型.

java 因为类型擦除的原因,在有需要知道泛型类型的情况下,无法操作,变通一点是可以传入一个 Class<T> 类型的参数.但是 kt 中可以通过 reified 解决这个问题.

<T> void check(Object item, Class<T> type) {
    if (type.isInstance(item)) {
        System.out.println(item);
    }
}

inline fun <reified T> printIfTypeMatch(item: Any) {
    if (item is T) {
        println(item)
    }
}

kotlin 编译后依旧是跑在 jvm 上,kt 是怎么做到的? 花样就在 inline 上面,简而言之上面的代码编译再反编译成 java 代码,并不包含泛型.编译时 kt 的编译器就将泛型替换成了实际的类型,没有了泛型,运行时自然能得到参数的实际类型.(不知道理解的对不对),具体可以参照 Kotlin的独门秘籍Reified实化类型参数-上篇Kotlin的独门秘籍Reified实化类型参数(下篇).

注意: reified 只能用于内联函数.且有别的限制.

  • java 中无法调用带有 reified 的 kt 函数.(单纯的内联函数也有限制,java 可以调用,但是会失去内联的效果)
  • 参数使用限制
    • 不能使用 非实化类型形参 作为类型实参 调用带实化类型参数 的函数.(???)
    • 不能使用 实化类型参数 创建实例对象.(可以用反射绕过)
    • 不能调用 实化类型参数 的伴生对象方法
    • reified 关键字只能用于内联函数,不能作用与类和属性.

一些应用

  • 直接判断变量是不是 泛型的类型,示例如上.

  • 传入泛型的类型

    • json 反序列化时通常需要对象的类型,例如 User user = new Gson().fromJson(getJson(), User.class);

    • 但在 kt 中就能玩一手.

      inline fun <reified T> Gson.fromJson(json: String) = fromJson(json, T::class.java)
      val user: User = Gson().fromJson(json)
      

其他

上文提到了,”真”泛型无法直接实例化一个对象.但是我们已经可以拿到泛型对应的 Class类了,因此可以通过反射可以绕过这个限制.

默认无参构造方法,最简单的 newInstance(),最终是调用那个无参的构造方法返回一个实例.也可以通过先获得那个无参的构造方法,择机调用.

val t: T = T::class.java.newInstance()
//or
val c = T::class.java.getConstructor()
val t: T = c.newInstance()
//or
val c = T::class.java.getDeclaredConstructor()
val t: T = c.newInstance()

有参数的构造方法,这个需要你知道有参的构造方法具体是那些参数,然后才能获取对应的构造函数.

// 可以通过反射获取一组参数的 type,传入 getConstructor/getDeclaredConstructor 获取构造方法,再传入参数,调用构造方法.
val c = T::class.java.getConstructor(listType)
val t: T = c.newInstance(list)
//or
val c = T::class.java.getDeclaredConstructor(listType)
val t: T = c.newInstance(list)

getConstructor/getDeclaredConstructor 区别

  • getConstructor 只返回 public 的构造方法.
  • getDeclaredConstructor 会返回所有,包括 privacy.使用私有构造方法有时需要设置 isAccessible = true

标签:java,val,Kotlin,List,类型,kotlin,泛型,转载
来源: https://www.cnblogs.com/winterreisender/p/15860396.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有