开场白
Kotlin是个不错的语言,但也有负面消息。我对它唯一的负面印象是有些语法太简单和抽象以至于不会kotlin的人会很懵逼。你可能会说不会当然懵逼了,但是你去看c++,java这些高级语言,即便不会只要有编程基础都能看个大概。 总之我认为kotlin就是一个精干的语言。泛型在kotlin家族里算是一个稍微抽象的东西了,有时候你很可能想简单会用即可,在实际编程中也可能确实如此,但当你想深入了解一些kotlin源码的时候,你就会发现它大量使用了泛型的特性和语法,所以还是很有必要更深入的了解清楚的好。
官网文档
大多数时候学习一个东西最好的地方就是官方文档
但很多时候可能看完了并不太明白,还需要一些自己总结和实验,本文就是这种总结和实验,所以我假设你已经看完了这个官方文档了
最普通的泛型
最普通的泛型应用几乎没什么可说的,这在众多高级语言里都有这种东西,为了全面我们还是说一下
class MyArray<T>
这就是一个最简的kotlin泛型类了,我们把它丰富一下,让程序能用它输出点什么
class MyArray<T>{
val arr = ArrayList<T>()
operator fun get(index:Int):T{
return arr[index]
}
operator fun set(index: Int, newValue:T){
arr[index] = newValue
}
}
这代码几乎没什么好说的,就是加了一个类变量arr,以及相当于重载了[]和=号的类方法, 在main函数用用他
val myArray = MyArray<Int>()
myArray.arr.addAll(listOf(1, 2, 3))
println(myArray[2])//输出 3
很简单吧,一切如预期。
out in 出场
我本来想逐个结合实例来说明out 和 in但感觉没这个必要,这俩关键字看官方文档足够了咱们只做个最终总结,然后稍微详细的说说星投影
out T<==>T可以是T或者T的子类 相当于java的?extends
int T<==>T可以是T或T的父类 相当于java的?super
我觉得过多的语言反而会让人迷惑,比如消费者 生产者什么的,记住以上两条即可
至于官文提到的使用处形变其实原理没变,只是out in出现的位置不同而已
星投影
看着挺吓人的是吧,别以为这个是个类似星星错综复杂就是了,这个“星”纯粹就是字面意思的“*”号
关于这个我们先来看看官网文档原文:
Star-projections
Sometimes you want to say that you know nothing about the type argument, but you still want to use it in a safe way. The safe way here is to define such a projection of the generic type, that every concrete instantiation of that generic type will be a subtype of that projection.
Kotlin provides so-called star-projection syntax for this:
-
For
Foo<out T : TUpper>
, whereT
is a covariant type parameter with the upper boundTUpper
,Foo<*>
is equivalent toFoo<out TUpper>
. This means that when theT
is unknown you can safely read values ofTUpper
fromFoo<*>
. -
For
Foo<in T>
, whereT
is a contravariant type parameter,Foo<*>
is equivalent toFoo<in Nothing>
. This means there is nothing you can write toFoo<*>
in a safe way whenT
is unknown. -
For
Foo<T : TUpper>
, whereT
is an invariant type parameter with the upper boundTUpper
,Foo<*>
is equivalent toFoo<out TUpper>
for reading values and toFoo<in Nothing>
for writing values.
If a generic type has several type parameters, each of them can be projected independently. For example, if the type is declared as interface Function<in T, out U>
you could use the following star-projections:
-
Function<*, String>
meansFunction<in Nothing, String>
. -
Function<Int, *>
meansFunction<Int, out Any?>
. -
Function<*, *>
meansFunction<in Nothing, out Any?>
.
我们看到它主要说了3条,我们来一条一条的看看
第一条:
- For
Foo<out T : TUpper>
, whereT
is a covariant type parameter with the upper boundTUpper
,Foo<*>
is equivalent toFoo<out TUpper>
. This means that when theT
is unknown you can safely read values ofTUpper
fromFoo<*>
.
对于 Foo <out T : TUpper>,其中 T 是一个具有上界 TUpper 的协变类型参数,Foo <*> 等价于 Foo <out TUpper>。 这意味着当 T 未知时,你可以安全地从 Foo <*> 读取 TUpper 的值。
为了演示它我们先来定义三个很简单的类
open class Animal(val name:String)
open class Person(name:String):Animal(name)
class Worker(name:String):Person(name)
然后按照条目中的说法在定义一个Star1类拥有泛型Foo <out T : TUpper>的样式 如下
class Star1<out T:Animal>(val u:T){
}
那么我们现在编写一个简单的函数来试用一下条目1的星投影
fun printStar1(star1: Star1<*>){
println(star1.u.name)
}
Star1<*>的类型定义就是星投影了,按照条目的文字这个函数定义等价于
fun printNormal1(star1:Star1<out Animal>){
println(star1.u.name)
}
我们实际调用下试试看就行了
val anim = Animal("Animal")
val person = Person("Person")
val worker = Worker("Worker")
val starAnim1 = Star1(anim)
val starPerson1 = Star1(person)
val starWorker1 = Star1(worker)
printStar1(starAnim1)
printStar1(starPerson1)
printStar1(starWorker1)
看看输出一切正常
条目2
- For
Foo<in T>
, whereT
is a contravariant type parameter,Foo<*>
is equivalent toFoo<in Nothing>
. This means there is nothing you can write toFoo<*>
in a safe way whenT
is unknown.
这一条没啥说的似乎 直接看看测试代码即可了
class Star2<in T>{
operator fun compareTo(other: T): Int = 0
}
fun printStart2(x:Star2<*>){
x.compareTo(1)//编译不过,因为需要nothing类型的参数,言外之意是你传什么都不对
}
fun printNormal2(x:Star2<in Nothing>){
x.compareTo(1)//编译不过,因为需要nothing类型的参数,言外之意是你传什么都不对
}
条目3
- For
Foo<T : TUpper>
, whereT
is an invariant type parameter with the upper boundTUpper
,Foo<*>
is equivalent toFoo<out TUpper>
for reading values and toFoo<in Nothing>
for writing values.
这个自己试试啊,其实就是相当于读的时候相当于第一条,写的时候相当于第二条
在看看底下的各种映射,其实无非都是在说明 <*>代表什么
除了上面说的 还有一个 foo<T>的话 foo<*>就代表 foo<out any?>
说了半天星投影,你可能会说,啥乱七八糟的,这玩意有毛用啊。 好我们来看一个需求,我们要给Array写一个扩展函数,这个函数的目的是分析Array中元素的类型,用指定类型的元素形成一个新的Array并返回它,这个怎么搞呢?
我们来看这段代码
val arr = arrayOf<Any>(1, 1.1, 2, 2.2, 3, 3.3)
arr.map { print("$it ") }
val arr2 = arr.myArrFilter<Double>()
println(arr2)
我们想通过myArrFilter这个函数挑出所有的double类型的数,形成一个新的数组arr2
[1.1, 2.2,3.3]
那怎么实现这个myArrFilter扩展函数呢?这个显然是对各种类型都可以用的你不能这样
Array<Double>.myArrFilter = .... 这样只对Double类型的array能用,那怎么模糊化它呢,那就是我们*了下面是具体代码
inline fun <reified R> Array<*>.myArrFilter(): List<R> {
val destination = ArrayList<R>()
for (element in this) if (element is R) destination.add(element)
return destination
}
你加了这个扩展后,程序按照预期输出了,但实际上这是Array已经提供了的功能,你可以自己看看它的原始实现,我贴一下,结束本篇讨论吧
public inline fun <reified R> Array<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R> {
return filterIsInstanceTo(ArrayList<R>())
}
public inline fun <reified R, C : MutableCollection<in R>> Array<*>.filterIsInstanceTo(destination: C): C {
for (element in this) if (element is R) destination.add(element)
return destination
}
网友评论