Kotlin泛型类型参数
泛型允许你定义带类型参数的类型。当这种类型的实例被创建出来的时候,类型参数被替换成称为类型实参的具体类型。例如,如果有一个List类型的变量,弄清楚这个列表中可以存储哪种事物是有意义的。类型参数可以准确清晰地进行描述,就像这样“这个变量保存了字符串列表”,而不是“这个变量保存了一个列表”。Kotlin说明“字符串列表”的语法和Java看起来一样:List<String>。还可以给一个类声明多个类型参数。例如,Map类就有键类型和值类型这个两个参数类型:class Map<K,V>。我们可以用具体的类型实参来实例化它:Map<String,Person>。目前,多有概念都和Java没什么不一样。
和一般类型一样,Kotlin编译器也常常能推导出实参类型:
val authors= listOf("Dmitry","Svetlana")
因为传给listOf函数值都是字符串,编译器推导出你正在创建一个List<String>。另一方面,如果你想创建一个空的列表,这样就没有任何可以推导出类型实参线索,你就要显示的指定它。就创建列表来说,即可以选择在变量声明中说明泛型的类型,也可以在穿件列表的函数中说明类型实参。
val readers: MutableList<String> = mutableListOf()
val readers = mutableListOf<String>()
两种声明是等价的。
1.泛型函数和属性
如果要编写一个很使用列表的函数,希望它可以在任何列表中使用,而不是某个具体类型的元素列表,需要编写一个泛型函数。泛型函数有它自己的类型参数。这些类型形参在每次函数调用时都必须替换成具体的类型实参。
大部分使用集合的库函数都是泛型的。
fun <T> List<T>.slice(indices:IntRange):List<T>
接收者和返回类型用到了函数类型的形参T,它们的类型都是List<T>。当你在一个具体的列表上调用这个函数时,可以显示地指定实参。但是大部分情况下你不必这样做,因为编译器会推导出类型。
调用泛型函数
val letters = ('a'..'z').toList()
LogS(<Char>(0..2))//[a, b, c]
LogS((10..13))//[k, l, m, n]
这两次调用的结果都是List<Char>。编译器吧函数返回类型List<T>中的T替换成推导的类型Char。
调用泛化的高阶函数
val authors = listOf("Dmitry", "Svetlana")
val readers = mutableListOf<String>(/*....*/)
fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>
{ it in authors }
这个例子中自动生成的lambda参数it的类型是String。编译器必须把它推导出来:毕竟,在函数声明中lambda参数是泛型类型T。编译器推断T就是String,因为它知道函数应该在List<T>上调用,而它的接受者readers的真实类型是List<String>。
可以给类或接口的方法、顶层函数,以及拓展函数声明类型参数,在前面的例子中,类型参数用在了接收者和lambda参数的类型上。
还可以用同样的语法声明泛型的拓展属性。例如下面这个返回列表倒数第二个元素的拓展属性:
val<T> List<T>.penultimate:T
get() = this[size-2]
LogS(listOf(1,2,3,4).parallelStream())
2.声明泛型类
和Java一样,Kotlin通过在类名称后加上一对尖括号,并把类型参数放在尖括号内来声明泛型类及泛型接口。一旦声明之后,就可以在类的主体内像其他类型一样使用类型参数。我们来看看标准java接口List如何使用Kotlin来声明。我们省去了大部分方法定义,让例子变得简单:
interface List<T>{
operator fun get(index:Int):T
}
如果你的类继承了泛型类,你就得为基础类型的泛型形参提供一个类型实参。它可以是具体类型或者另一个类型形参:
class StringList: <String> {
override fun get(index: Int): String ="..."
}
class String:Comparable<String>{
override fun compareTo(other: String): Int =2
}
StringList类型声明成只能包含String元素,所以它使用String作为基础类型的类型实参。子类中的任何函数都要用正确的类型替换掉T,所以在StringList中你会得到函数签名get(Int):Srting,而不是fun get(Int):T。
而类ArrayList定义了它自己的类型参数T并把它指定为父类的类型实参。注意ArrayList<T>中的T和List<T>中的T不一样,它是全新的类型实参,不必保留一样的名称。
一个类甚至可以把它自己作为类型实参引用。实现Comparable接口的类就是这种模式的经典例子。任何可以比较的元素都必须定义如何与同样类型的对象比较:
interface Comparavle<T>{
fun compareTo(other:T):Int
}
class String:Comparable<String>{
override fun compareTo(other: String): Int =/*q*/
}
String类实现了Comparable泛型接口,提供类型String给类型实参T。
迄今为止,泛型和Java中看起来差不多。
3.类型参数约束
类型参数约束可以限制作为(泛型)类和(泛型)函数的类型实参的类型。以计算列表元素之和的函数为例。它可以用在List<Int>和List<Double>上,但不可以用在List<String>这样的列表上。可以定义一个类型参数约束,说明sum的类型形参必须是数字,来表达这个限制。
如果你把一个类型指定为泛型类型形参的上界约束,在泛型类型具体的初始化中,其对应的类型实参就必须是这个具体类型或者它的子类型。
你是这样定义约束的,把冒号放在类型参数名称之后,作为类型参数上界的类型紧随其后。在Java中,用的是关键字extends来表达一样的概念:<T extends Number> T sum(List<T> list)。
这次函数调用时允许的,因为具体类型实参继承了Number:
一旦指定类型形参T的上界,你就可以把类型T的值当做它的上界(类型)的值使用。
fun <T : Number> oneHalf(value: T): Double {
return () / 2.0
}
LogS(oneHalf(3))//1.5
声明带类型参数约束的函数
fun <T:Comparable<T>> max(first:T,second:T):T{
return if (first>second) first else second
}
LogS(max("kotlin","java"))//kotlin
当你试图对不能比较的条目条用max方法时,代码不会编译:
LogS("kotlin",42)
T的上界是泛型类型Comparable<T>。前面已经看到了,String类型继承了Comparable<String>,这使得String变成了max函数的有效类型实参。
记住,first>second的简写形式会根据Kotlin的运算符约定编译成(second)>0。这种比较之所以可行,是因为first的类型T继承自Comparable<T>,这样你就可以比较first和另外一个类型T的元素。
极少数情况下,需要在一个类型参数上指定多个约束,这时你需要使用稍微不同的语法。
为一个类型参数指定多个约束
fun <T> ensureTrailingPeriod(seq:T)
where T:CharSequence,T:Appendable{
if (!('.')){
('.')
}
}
val helloWorld=StringBuilder("Hello world")
ensureTrailingPeriod(helloWorld)
LogS(helloWorld)//Hello world.
这种情况下,可以说明作为类型实参的类型必须实现CharSequence和Appendable两个接口。这意味着该类型的值可以使用访问数据和修改数据两种操作。
4.让类型形参非空
如果你声明的是泛型类或者泛型函数,任何类型实参,包括那些可空的类型实参,都可以替换它的参数类型实参。事实上没有指定上界的类型形参将会使用Any?这个默认上界。
class Processor<T>{
fun process(value:T){
value?.hashCode()
}
}
process函数中,参数Value是可空的,尽管T并没有使用问号标记。下面这种情况是因为Processor类具体初始化时T能使用可空类型:
al nullableStringProcess=Processor<String?>()
(null)
如果你想保证替换类型形参的始终是非空类型,可以通过指定一个约束对象来实现。如果你除了可控性之外没有任何限制,可以使用Any代替默认Any?作为上界:
class Processor<T:Any>{
fun process(value:T){
value?.hashCode()
}
}
约束<T:Any>确保了类型T永远是非空类型。编译器不会接收代码Processor<Stirng?>,因为类型实参String?不是Any的子类型。
注意可以通过指定任意非空类型作为上界,来让类型参数非空,不光是类型Any。
到目前为止,我们已经介绍了泛型的基础概念——那些和Java最接近的主题。