Kotlin-泛型

2018-05-10  本文已影响11人  风雨渡江

关于什么是泛型, 看另一篇文章 https://www.jianshu.com/p/7eac2f36036e

参数化函数(泛型函数)

  1. 在函数名前边加上<T>实现参数化函数
  2. 函数的参数至少有一个是T类型, 否则编译器无法推断出T的类型
fun <T> foo (input: T): T {
    return input
}

函数的返回值可以是任何类型, 但不能用运行时的类型

fun <T> foo (input: T): String {
    return input // 即使我们在运行时传入了一个字符串也不行, input的类型是T, 与运行时的类型无关
}

参数化类型

参数化类型主要用在各种容器类型上, 这种类型内部可以包含其他类型的数据, 比如list, map

class Sequence<T> // 定义一个参数化类型
val seq = Sequence<Int>() //传入Int类型

上界类型约束

将类型限制为某个类的子类, 如果省略, 将会是Any

fun <T: Number> convert(a: T, b: T)  // 将T的类型限制为Number的子类
fun <T> convert(a: T, b: T)  // T的类型限制为Any

Invariance (不变)

类型不变指泛型类型默认是没有继承上的关系的, M<Int>并不是M<Number>的一个子类型.
这样设计的原因, 参考这个例子:

fun foo(m: M<Number>): Unit {
  m.add(123L)
}

假设有一个M<Int>型的集合x, 执行foo(x)后, 如果类型有继承关系, 由于IntLong都是Number的子类型, 就会往x里添加一个Long型元素, 这违背了类型安全的原则.

Covariance (协变)

协变是改变类型之间的关系, 使他们有继承性.

fun foo(m: M<Number>): Unit {
  m.functionFromNumber()
}

假设foo函数会调用一个Number类的方法, 这样我们就不用管传入的是m<int>, 还是m<Long>都无所谓(因为函数定义在父类上), 此时就需要协变`来让类型有继承关系.

class M<out T>

使用outT定义为协变类型之后, 不能用T作为函数的输入参数(形参), 可以做返回值. 像m.add(T)是非法的.

逆变 Contravariance

逆变是反转两个类的继承关系, 比如逆变后, M<Int> 是 M<Number> 的父类.

这个需求是这样的

Event<String>(stringHandler)
Event<Number>(numberHandler)

我们有两种Event, 分别用对应类型的Handler处理, 假设我们想用一个通用的handler比如: commonHandler<Any>`来处理.

Event<String>(commonHandler)
Event<Number>(commonHandler)

答案是不行, 因为AnyString的父类, 而Event只能使用String或它的子类. 通过逆变, Any就变成了String的子类.

class Event<in T>(val handler: Handler<T>)
class Handler<in T>
...

逆变后, 类型只能作为输入参数(形参), 不能作为返回值类型.

type projection

类型的variance有两种, use site(java) 和 declaration site(Kotlin), type projection 允许我们使用use site variance, 它的思想是, 如果我们无法在类定义(比如使用别人的类)时使用covariance或contravriance(即declaration site), 我们可以在定义函数时, 规定函数将如何使用类型.

fun foo(m: M<out Number>): Unit {
  m.functionFromNumber()
}

同理, contravariant

class Event<in T>(val handler: Handler<in T>) // 构造函数的in T

type projection定义了我们如何使用函数, 而不需修改原类型声明

Type reification

由于JVM在编译时会把所有泛型类型(不包括基础数据类型)信息擦除, kotlin同样如此, 使用Type reification, kotlin可以为inline 函数保留泛型类型信息.

inline fun <reified T>printT(any: Any): Unit {
    if (any is T)
    println("I am a tee: $any")
}

这个函数可以在运行时获取到T的传入类型. 类型具体化只能用于inline函数, 因为inline函数的执行是把函数体内容直接拷贝到调用处, 此时通过传递的参数可以知道T的类型. 其他函数无法使用类型具体化

上一篇下一篇

猜你喜欢

热点阅读