Kotlin入门-似曾相识的泛型

在这里插入图片描述

前言

在Kotlin中,总的来说,可以理解泛型为:
①向上解决类型不通用
②向下解决类型限定

如果看着累,建议先看小结,寥寥几字,大致了解下。

需要理解几个问题?
① 理解 型变是什么?逆变又是什么?
② 泛型存在的价值是什么?
③ 泛型注解out、in有什么用?Invariant又是什么?
④ 类型擦除 有什么影响?

本节的目录结构是这样的

  • 泛型说明
  • 型变
  • 类型投影
  • 泛型函数
  • 泛型约束
  • 类型擦除

泛型说明

Kotlin的泛型,功能与 Java 一样

看一个范例
fun main(args: Array<String>) {
    val age = 23
    val name = "runoob"
    val bool = true

    doPrintln(age)    // 整型
    doPrintln(name)   // 字符串
    doPrintln(bool)   // 布尔型
}

fun <T> doPrintln(content: T) {
    when (content) {
        is Int -> println("整型数字为 $content")
        is String -> println("字符串转换为大写:${content.toUpperCase()}")
        else -> println("T 不是整型,也不是字符串")
    }
}

输出了什么呢

整型数字为 23
字符串转换为大写:RUNOOB
T 不是整型,也不是字符串

这便是泛型了,范例中允许接受不同的类型,类型通用。


型变

即类型转变。通过转变类型,提升API的适配度。

java为什么需要通配符来提升API的灵活性?

我们来看看Java的型变是怎么样的。

  • Java的泛型是不型变的
  • 要想实现多类型的列表存储,就需要有很多个不同的定义,这就很麻烦了
  • 必须要有一个 xxx 提供一个通用的解决方案,
    否则,列表跟数组又有何区别~

声明处型变(declaration-site variance)

即在声明变量、参数时就确定T的类型。

说起来有点绕口,来看范例,进行一下对比

// Java

void demo(Source<String> strs) {
  Source<Object> objects = strs; // !!!在 Java 中不允许
  // ……
}

// Kotlin

interface Source<out T> {
    fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
    // ……
}

注意看这里Source部分

  • java是不支持直接转变的
  • Kotlin支持

在Source是使用处,Kotlin的型变解决了 子类型化的麻烦。
这里引入一个概念,型变注解:这里的out
同样的型变注解还有 in


类型投影(type projections)

投影就是 某事物的影子,影子之下才有天地。
只有在影子内的东西方可以动。

说的就是 限定了。

使用处型变:类型投影

这讲的是一种将 某个类型投影 到某处。
你想想一下,只允许投影之下的暗处,这不正是限制吗?

类型投影,不就是类型限制吗?怎么限定呢?通过out\in的类型注解。

来看个范例
fun copy(from: Array<Any>, to: Array<Any>) {
    assert(from.size == to.size)
    for (i in from.indices)
        to[i] = from[i]
}
fun main() {
   val ints: Array<Int> = arrayOf(1, 2, 3)
   val any = Array<Any>(3) { "" } 
   copy(ints, any)  //  这里编译是不通过的,ints类型为 Array<Int> 但此处期望 Array<Any>
}
会碰上什么问题呢?

Array 在 T 上是不型变的,因此 Array 和 Array 都不是另一个的子类型。

为什么? 再次重复,因为 copy 可能做坏事。为了避免这种错误,编译器禁止了这种操作。

我们唯一要确保的是 copy() 不会做任何坏事。我们想阻止它写到 from去。

fun copy(from: Array, to: Array) { …… }

这便是类型投影。所有out(协变)是输出,in(逆变)是输入。

复习一下out、in

对于 out 泛型,可以将使用子类泛型的对象 -> 赋值给使用父类泛型的对象。
对于 in 泛型,可以将使用父类泛型的对象 -> 赋值给使用子类泛型的对象。

星投影

这使用于并不知道类型参数的任何信息。 * 代表通吃。
这个泛型类型的所有的实体实例, 都是这个投射的子类型。

看个例子
Function<*, String>

这里用到了*

var list:ArrayList<> = arrayListOf(1) //<>必不可少 相当于java的无泛型
当 cList:ArrayList<> = javaList 时<>相当于
当 javaList:ArrayList<> = cList 时<>相当于

这就是什么都接收了


泛型函数

在简单的学习下,泛型的函数是怎么编写的

fun <T> singletonList(item: T): List<T> {
    // ……
}

fun <T> T.basicToString(): String {  // 扩展函数
    // ……
}

这么用
val l = singletonList(1)
可以省略能够从上下文中推断出来的类型参数
val l = singletonList(1)


泛型约束

这跟java是类似的
//泛型约束 <占位符:类型>

fun <T:Number> play(vararg param: T):Double{
    return param.sumByDouble { it.toDouble() }
}

//多个约束,T有多个上限 , where T:类型,T:类型

fun <T> getBetterBig(list:Array<T>,threhold:T):List<T> where T:Number,T:Comparable<T>{
    return list.filter { it>= threhold }.sorted()
}

类型擦除

泛型声明的类型安全检测是仅在编译期进行的。
例如,Foo 与 Foo<Baz?> 的实例都会被擦除为 Foo<*>。

运行时泛型类型的实例不保留关于其类型实参的任何信息。

运行时不保留其类型实参的任何信息,即类型擦除。

编译器会禁止由于类型擦除而无法执行的 is 检测

看个熟悉的范例
fun handleStrings(list: List<String>) {
    if (list is ArrayList) {
        // `list` 会智能转换为 `ArrayList<String>`
    }
}

泛型函数调用的类型参数也同样只在编译期检

这便是
在编译器会进行安全类型检查,在运行期则擦除类型,相当于是*类型


小结

泛型,牵扯到 Out (协变)、In(逆变)、Invariant(不变),这是必须要理解的部分。

所谓泛型,即 类型限制,类型转变的称呼。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值