Kotlin泛型

泛型的由来和作用

有许多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类(集合类)—— 《Java编程思想 (4)》

通常情况的类和参数,开发者只需要使用具体类型的即可 —— 基本类型和自定义的类。

在集合类的场景下,需要编写可以应用于多种类型的代码,如果针对每种类型都写一套代码,那么代码的复用率很低,抽象也没有做好 —— 为什么不把“类型”也抽象成参数呢?

Java5引入的泛型机制实现了“参数化类型”,将原来的具体类型 参数化,将类型定义成参数。

  • 泛型提升了代码的抽象程度,提高了代码的复用率;
  • 泛型的优点是让编译器追踪参数类型,执行类型检查和类型转换,避免出错。
    泛型可以用来限制集合类持有的对象类型,使得类型更加安全。当在集合中放入错误的类型对象时,编译器会报错。

有了泛型,可以开发出更强大、更安全的类型检查,无须手工进行类型转换,并且可以开发出更加通用的代码。

使用泛型

泛型接口

/**
 * 泛型接口,
 * 类型参数放在接口名后面 <T>
 */
interface Generator<T> {
    // 函数中使用类型 T
    operator fun next() :T
}

// 测试
fun testGenerator(): Unit {
    // 对象表达式
    // 使用 object 关键字声明接口Generator的实现类
    val gen= object : Generator<Int> {
        // 在lambda表达式中 实现 next()函数
        override fun next(): Int {
            return Random().nextInt(100)
        }
    }

    println(gen.next())
    println(gen.next())
    println(gen.next())
}

泛型类

/**
 * 声明一个带类型参数的类
 */
class Container<K, V>(var key: K, var value: V) {
    override fun toString(): String {
        return "Container{key=$key, value=$value}"
    }
}

// 测试
fun testContainer(): Unit {
    val c1 = Container<Int, String>(12, "XYZ") // 具体化<Int, String>
    println(c1)

    val c2 = Container("UID", 6.66) // 类型推断,省略<String,Double>内容
    println(c2)
}

泛型函数

在 函 数 中 直 接 声 明 泛 型 参 数 在函数中直接声明泛型参数

声明泛型函数

// 函数 1
fun isEqualInt(a: Int, b: Int): Boolean = a==b
// 函数 2
fun isEqualStr(a: String, b: String): Boolean = a==b

函数 1、2,除了函数名、参数类型外,函数体内容、返回值类型都是一样的,
这样的写法代码复用率很低,可以采用泛型函数来优化:

fun <T> isEqual(a: T, b: T): Boolean = a==b

<T>声明类型参数T 是类型参数。

泛型中的类型参数,可以是小写或大写的字母,一般情况建议使用大写英文字母。

多类型参数

多个泛型类型参数之间使用逗号“,”隔开。

fun <X,Y,Z> logs(a: X, b: Y, c: Z) = println("log info {$a, $b, $c}")

// 测试
fun testLogsF(): Unit {
    logs("Jack", 18, "student") // 输出:log info {Jack, 18, student}
    logs("Lucy", 19, 168.5) // 输出:log info {Lucy, 19, 168.5}
}

logs() 函数定义了3种类型,X,Y,Z。

泛型约束

函数isEqual() 存在一个问题,不是所有类型的T都具有可比性,最好限定T的类型范围。

// 限定T的类型范围 —— T 必须都是继承自接口Comparable<T> 的类型.
fun <T :Comparable<T>> isEqualPro(a: T, b: T): Boolean = a==b

这里的Comparable是类型T的上界,类型参数T代表的是实现了Comparable接口的类。

可空类型参数

泛型函数声明中,参数类型如果没有泛型约束,则表示函数可以接收任何类型的数据作为参数,包含可空和非空数据。

fun <T> isEqual(a: T, b: T): Boolean = a==b

fun testIsEqual(): Unit {
    isEqual(2,9)
    isEqual(null, 7)
}

如果不想接收空类型的数据,可以使用Any作为约束条件:

// 采用Any作为约束条件,限制函数只能接收非空数据
fun <T: Any> isEqualWithoutNull(a: T, b: T): Boolean = a==b

协变与逆变

Java和Kotlin 的协变逆变

Java中

List<Integer> ints = new ArrayList<>();
List<Object> objs = ints; // 这里会报错,无法编译

ObjectInteger的父类型,Object类型大于Integer
Java中不支持型变,不能将 List<Integer> 赋值给 List<Object>.

  • 使用上界通配符:<? extends T>
List<Integer> ints = new ArrayList<>();
List<? extends Object> objs = ints; // 这里可以编译通过

ObjectInteger的父类型,Object类型大于Integer
使用上界通配符后,List<? extends Object> 的类型就大于 List<Integer> 的类型了,就实现了协变

协变的定义:
如果类型 A大于B,经过一个变化trans后得到的 trans(A) 也是大于trans(B) 的,那么称之为协变

  • 使用下界通配符:<? super T>
List<Object> oList = new ArrayList<>();
List<? super Integer> iList = oList;  // 这里可以编译通过

ObjectInteger的父类型,Object类型大于Integer
List<? super Integer> 类型大于 List<Object>,类型关系反转了,这里实现了逆变

逆变的定义:
如果类型 A大于B,经过一个变化trans后得到的 trans(A) 小于trans(B) ,那么称之为逆变

对应的在Kotlin中

val ints = ArrayList<Int>()
val objs: ArrayList<Any> = ints // 编译不通过

增加 out 关键字:

val ints = ArrayList<Int>()
val objs: ArrayList<out Any> = ints // 这里可以编译通过

这里实现了协变。

in 关键字:

val oList = ArrayList<Any>()
val iList: ArrayList<in Int> = oList // 这里可以编译通过

这里实现了逆变。

Java通配符与Kotlin投射类型

Java的泛型使用到了通配符,用 ? extends T 指定类型参数的上界,用 ? super T 指定类型参数的下界;
Kotlin中没有了这个通配符,使用投射类型(projected type)来实现与Java通配符相同的功能。使用 out T 指定类型参数的上界,代表生产者对象;使用 in T 指定类型参数的下界,代表消费者对象.

看一段代码:

// java.util.Collections 的 copy() 函数
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
}

这段代码中,List<? super T> dest 是消费数据的对象,数据会被写入 dest 对象中,它保证写入数据时类型安全,在kotlin中用 in T标记;
List<? extends T> src 是生产者,提供数据的对象,它保证读取时类型安全,在kotlin中用 out T标记;

out T 相当于 ? extends T
in T 相当于 ? super T

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值