Kotlin的泛型:泛型类型参数

Kotlin 的泛型是指带类型形参的类型。当泛型的实例被创建时,类型形参被替换为具体类型。

以 Kotlin 的 list 方法为例,当创建一个字符串列表时,类型形参 T 被替换为具体类型 String。

val strList: List<String>
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T> = MutableList(size, init)

除了 List,还可以给一个类型声明多个类型形参,比如 Map 有 Key 和 Value 两个类型形参。

val map : Map<String, Person>
public interface Map<K, out V>

和 Kotlin 的普通类型一样,泛型也支持类型推导。

val authors = listOf("Dmitry", "Svetlana")

authors 没有显示指明它是字符串列表,但是因为 list 里面有两个字符串,所以被推导为字符串列表类型。

但是如果是一个空列表,就必须指定类型参数。因为编译器也不知道列表里面是什么类型的值,无法推导泛型的类型参数。

    val readers: List<String> = mutableListOf()

也可以在 mutableListOf 指定类型实参。

    val readers = mutableListOf<String>()

Kotlin 的泛型类型实参和 Java 的类型实参有一些区别。

Kotlin 必须显式指定类型实参或者可以被推导出类型实参。而 Java 可以不指定类型实参。

Java 可以这么写:

List list = new ArrayList();

但是 Kotlin 不行。这是因为 Java 的泛型是 Java 1.5 版本引入的特性,为了兼容老版本,它必须可以写成不带类型实参的形式。而 Kotlin 从一开始就设计了泛型,必须定义类型实参。

泛型函数和属性

泛型函数

大部分使用集合的库函数都是泛型的。比如 slice。

public fun <T> List<T>.slice(indices: IntRange): List<T> {
    if (indices.isEmpty()) return listOf()
    return this.subList(indices.start, indices.endInclusive + 1).toList()
}

fun 后面的 表示类型形参声明。

List 表示接收者是 List 类型,一个由 T 类型组成的列表。slice 函数是这个 List 类型的成员函数或者扩展函数。

: List 表示 slice 的返回值也是一个由 T 类型组成的列表。

调用泛型函数可以显式指定类型实参,也可以完全不写类型实参,由编译器推导类型。

    val letters = ('a'..'z').toList()
    // 输出[a, b, c]
    println(letters.slice<Char>(0..2))
	// 输出[k, l, m, n]
    println(letters.slice(10..13))

泛化的高阶函数

泛化的高阶函数是指函数参数为泛型函数的高阶函数。

filter 方法接收一个 (T) -> Boolean 类型的 predicate 函数,它的类型形参和接收者的类型形参相同。

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

调用 List 的 filter 方法。编译器推导出 it 的类型是 String,和接收者的类型参数相同。

private fun filter() {
    val authors = listOf("Dmitry", "Svetlana")
    val readers: List<String> = mutableListOf("Dmitry", "Svetlana", "zhangsan", "lisi")
    // 输出[zhangsan, lisi]
    readers.filter {
        it !in authors
    }.apply { println(this) }
}

泛型属性

和泛型函数一样,还可以用相同的语法声明泛型的扩展属性。

比如返回列表的倒数第二个元素。

val <T> List<T>.penultimate : T
    get() = this[size - 2]

不能声明泛型非扩展属性。因为普通属性不能存储多个不同类型的值,只有带接收者定义的扩展属性才有意义。

以下代码编译器会报错,属性的类型参数只能用在它的接收者类型。也就是扩展属性。

val <T> x: T = 1

声明泛型类

泛型类使用 <> 操作符定义类型参数。

List 接口定义了 T 类型的类型参数,T 类型可以作为 get 方法的返回值类型。

interface List<T> {
    operator fun get(index: Int): T
}

继承泛型类

继承泛型类有 2 种方法:

  1. 指定具体类型
  2. 继续使用类型参数

指定具体类型

class StringList : List<String> {
    override fun get(index: Int): String {
        TODO("Not yet implemented")
    }
}

继续使用类型参数

class ArrayList<T> : List<T> {
    override fun get(index: Int): T {
        TODO("Not yet implemented")
    }
}

继承类自身也可以是泛型的类型参数。

比如 String 类继承了 Comparable 接口,同时它也是 Comparable 的类型参数 。

interface Comparable<T> {
    fun compareTo(other: T): Int
}

class String : Comparable<String> {
    override fun compareTo(other: String): Int {
        TODO("Not yet implemented")
    }
}

类型参数约束

类型参数约束可以限制泛型的参数类型。比如对 List 的所有元素求和,可以对 List 或者 List 求和,但是无法对 List 求和。所以求和函数必须限制 List 的类型参数。

类型参数的上界

Kotlin 使用 : 限定类型参数的上界。与 Java 的 extend 类似。

fun <T : Number> List<T>.sum(): T

sum 方法的类型参数必需是 Number 的子类。

当类型参数的上界被指定后,可以调用上界的方法。

比如 oneHalf 调用了 Number 的 toDouble。

fun <T : Number> oneHalf(num: T): Double {
    return num.toDouble() / 2.0
}

fun main() {
    // 1.5
    println(oneHalf(3))
}

max 函数比较了两个值,因此它的类型参数必须实现了 Comparable 接口,上界为 Comparable。

String 类型实现了 Comparable,因此可以比较 “kotlin”, “java”。

fun <T : Comparable<T>> max(first: T, second: T): T {
    return if (first > second) {
        first
    } else {
        second
    }
}

fun main() {
    // kotlin
    println(max("kotlin", "java"))
}

类型参数的多重约束

泛型的类型参数可以设置多个约束。

比如类型参数 T 既继承了 CharSequence,也继承了 Appendable。这样它可以同时调用 endsWith 和 append。
endsWith 是 CharSequence 的方法,append 是 Appendable 的方法。

fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable {
    if (!seq.endsWith('.')) {
        seq.append('.')
    }
}

fun main() {
    val helloWorld = StringBuilder("Hello World")
    ensureTrailingPeriod(helloWorld)
    // Hello World.
    println(helloWorld)
}

让类型参数非空

泛型的类型参数可以为空类型。有时候需要约束泛型类型不为空,保证参数为非空类型。

在 process 内部使用 ?. 安全调用,保证 arg 为空时打印 “null arg”。

class Processor<T> {
    fun process(arg : T) {
        arg?.hashCode() ?: println("null arg")
    }
}

fun main() {
    val nullableProcessor = Processor<String?>()
    // null arg
    nullableProcessor.process(null)
}

更好的方法是指定类型参数的上界为 Any 类型。Any 类型是所有非空类型的父类。

class Processor<T : Any> {
    fun process(arg: T) {
        arg.hashCode()
    }
}

fun main() {
    // 编译错误:Type argument is not within its bounds.
    val nullableProcessor = Processor<String?>()
    nullableProcessor.process(null)
}

Processor 这里也可以不用 Any,只要是任何非空类型作为上界就行。

题外话:这里的示例直接调用 hashCode 也不会报错。因为 hashCode 也是一个扩展方法。

class Processor<T> {
    fun process(arg: T) {
        // 不会报错
        arg.hashCode()
    }
}

fun main() {
    val nullableProcessor = Processor<String?>()
    nullableProcessor.process(null)
}

hashCode 是 Any? 的扩展方法,如果接收者为空,直接返回 0。

HashCode.kt

package kotlin

import kotlin.internal.InlineOnly


/**
 * Returns a hash code value for the object or zero if the object is `null`.
 *
 * @see Any.hashCode
 */
@SinceKotlin("1.3")
@InlineOnly
public inline fun Any?.hashCode(): Int = this?.hashCode() ?: 0
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值