- kotlin值得系列1:内置类型。基本类型,数组,Range区间,集合框架,函数,Lambda
- kotlin值得系列2,关于类你所应该知道的一切
- kotlin值得系列3、函数加强之 高阶函数、内联函数、集合变换与序列、SAM转换、常用的高阶函数
- kotlin值得系列4、变量,常量,表达式,运算符
- kotlin值得系列5、关于kotlin泛型,也许值得看的文章
- kotlin值得系列6、kotlin的反射,走一个?
- kotlin值得系列7、也是该看看注解annotation了
- 泛型的基本概念
- 泛型约束
- 泛型的型变
- UnsafeVariance
- 星投影Start Projection
- 泛型的实现原理与内联特化
一、泛型的基本概念
泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上。
与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。
- 创建类的实例时我们需要指定类型参数
- kt泛型可以自动推导类型参数,省略类型参数
原理:类型擦除
- 伪泛型:编译时类型擦除,运行时无实际类型生成。比如Java,Kotlin
- 真泛型:编译时生成真实类型运行时也存在该类,比如C#,C++
创建类的实例时我们需要指定类型参数
class Box<T>(t : T) {
var value = t
}
fun main(args: Array<String>) {
var boxInt = Box<Int>(10)
var boxString = Box<String>("Hello")
println(boxInt.value)
println(boxString.value)
}
输出:
10
Hello
kt泛型可以自动推导类型参数,省略类型参数
fun <T> boxIn(value: T) = Box(value)
// 以下都是合法语句
val box4 = boxIn<Int>(1)
val box5 = boxIn(1) // 编译器会进行类型推断
再来一个
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 不是整型,也不是字符串
二、泛型约束
-
我们可以使用泛型约束来设定一个给定参数允许使用的类型。
-
Kotlin 中使用 : 对泛型的类型上限进行约束。最常见的约束是上界(upper bound)。
-
Kotlin 泛型更加简洁安全,但是和 Java 一样都是有类型擦除的,都属于编译时泛型
Java 使用 extends 关键字指明上界。
在 Kotlin 中使用:代替 extends 对泛型的的类型上界进行约束。
来个泛型约束的简单例子
fun <T : Number> sum(vararg param: T) = param.sumByDouble { it.toDouble() }
fun main() {
val result1 = sum(1,10,0.6)
val result2 = sum(1,10,0.6,"kotlin") // compile error
}
Kotlin 默认的上界是Any?。Any 类似于 Java 中的 Object,它是所有非空类型的超类型。但是 Any 不能保存 null 值,如果需要 null 作为变量的一部分,则需要使用Any?。Any?是 Any 的超类型,所以 Kotlin 默认的上界是Any?
对于多个上界约束条件,可以用 where 子句:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
三、泛型的型变
type variance(型变)
Kotlin 中没有通配符类型,它有两个其他的东西:
- 声明处型变(declaration-site variance)
- 类型投影(type projections)。
型变是指我们是否允许对参数类型进行子类型转换。
举个例子你就明白了,假设Orange类是Fruit类的子类,Crate 是一个泛型类,那么,Crate<Orange> 是 Crate<Fruit> 的子类型吗?
直觉可能告诉你,Yes。但是,答案是No。
对于Java而言,两者没有关系。
对于Kotlin而言,Crate<Orange>可能是Crate<Fruit>的子类型,或者其超类型,或者两者没有关系。
这取决于Crate<T>中的 T 在类Crate中是如何使用的。简单来说,型变就是指 Crate<Orange> 和 Crate<Fruit> 是什么关系这个问题,对于不同的答案,有如下几个术语。
-
invariance(不型变) :也就是说,
Crate<Orange>和Crate<Fruit>之间没有关系。 -
covariance(协变) :也就是说,
Crate<Orange>是Crate<Fruit>的子类型。 -
contravariance(逆变) :也就是说,
Crate<Fruit>是Crate<Orange>的子类型。
相当于说,invariance就是父与子没关系,covariance就是正常的父子,contravariance说父子关系反过来。
- Kotlin中的泛型类在定义时即可标明型变类型(协变或逆变,当然也可以不标明,那就是不型变的),在使用处可以直接型变(称为声明处型变)。
- 如果想要Kotlin的型变和Java兼容,那么就需要用到 类型投影(type projections) 的方式来处理型变。
声明处型变
-
声明处的类型变异使用协变注解修饰符:
in(逆变)、out(协变),消费者 in,生产者 out。 -
使用 out 使得一个类型参数协变,协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型:
-
in 使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型。(协变只能出现在返回值中,逆变只能出现在方法的参数中)
这有篇关于型变的文章说得不错。www.jianshu.com/p/0c2948f7e…
out 协变
还是上例子吧
open class Person(val name: String, val age: Int) {
open fun toWork() {
println("我是父类工人:$name,我要好好干活!!!")
}
}
class Worker1(name: String, age: Int) : Person(name, age) {
override fun toWork() {
println("我是电工:$name,接电线!!!")
}
}
class Worker2(name: String, age: Int) : Person(name, age) {
override fun toWork() {
println("我是木工:$name,做衣柜!!!")
}
}
fun setWork(workerList: List<out Person>) {
for (o in workerList) {
o.toWork()
}
}
fun main(args: Array<String>) {
val personArrayList: MutableList<Person> = ArrayList()
// 注意这里添加的 Person
personArrayList.add(Person("aaa", 11))
// 以下两个添加的是 Worker1 和 Worker2
personArrayList.add(Worker1("bbb", 12))
personArrayList.add(Worker2("ccc", 13))
setWork(personArrayList)
val personArrayList1: MutableList<Worker1> = ArrayList()
personArrayList1.add(Worker1("QQ", 14))
setWork(personArrayList1)
val personArrayList2: MutableList<Worker2> = ArrayList()
personArrayList2.add(Worker2("TT", 15))
setWork(personArrayList2)
}
输出:
我是父类工人:aaa,我要好好干活!!!
我是电工:bbb,接电线!!!
我是木工:ccc,做衣柜!!!
我是电工:QQ,接电线!!!
我是木工:TT,做衣柜!!!
.
.
再来一个例子
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
class SimpleData<out T>(val data: T) {
fun get(): T? {
return data
}
}
fun main() {
val stu = Student("liu",23)
val data = SimpleData<Student>(stu)
val studentData = data.get()
println(studentData!!::class.java)
}
.
.
in 逆变
例子1
class Reverse<in T> {
fun setValue(value: T) {
println("value: $value")
}
}
fun main() {
var reverse1 = Reverse<Int>()
val reverse2 = Reverse<Number>()
reverse2.setValue(3.14f) // value: 3.14
// 可以将 Reverse<Number> 赋值给 Reverse<Int>
reverse1 = reverse2
}
.
.
例子2
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
interface ITransform<in T> {
fun transform(t : T) : String
}
fun handleMyData(trans: ITransform<Student>) {
val student = Student("liu",23)
val result = trans.transform(student)
}
fun main() {
val trans = object : ITransform<Person> {
override fun transform(t: Person): String {
println("姓名: ${t.name} 年龄: ${t.age}")
return "${t.name} ${t.age}"
}
}
handleMyData(trans)
}
输出:
姓名: liu 年龄: 23
四、UnsafeVariance
@UnsafeVariance:型变点 违例
那是否意味着
out关键字修饰的泛型参数是不是不能出现在in位置 ?当然不是,只要函数内部能保证不会对泛型参数存在写操作的行为,可以使用UnSafeVariance注解使编译器停止警告,就可以将其放在in位置。out关键字修饰的泛型参数也是同理。
例如Kotlin的List中contains函数等,就是应用UnSafeVariance注解使泛型参数存在于in位置,其内部没有写操作。
public interface List<out E> : Collection<E> {
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
public operator fun get(index: Int): E
public fun indexOf(element: @UnsafeVariance E): Int
public fun lastIndexOf(element: @UnsafeVariance E): Int
public fun listIterator(): ListIterator<E>
public fun listIterator(index: Int): ListIterator<E>
public fun subList(fromIndex: Int, toIndex: Int): List<E>
}
五、星投影Start Projection
星号投影用来表明“不知道关于泛型实参的任何信息”。
类似于 Java 中的无界类型通配符?, Kotlin 使用星号投影*。
*代指了所有类型,相当于Any?。
例如:MutableList<*> 表示的是 MutableList<out Any?>
fun main() {
val list1 = mutableListOf<String>()
list1.add("string1")
list1.add("string2")
printList(list1)
val list2 = mutableListOf<Int>()
list2.add(123)
list2.add(456)
printList(list2)
}
fun printList(list: MutableList<*>) {
println(list[0])
}
}
正是由于使用 out 修饰以及星号投影的类型不确定性,会导致写入的任何值都有可能跟原有的类型冲突。因此,星号投影不能写入,只能读取。
参考: