泛型类型参数
编译器一般可以推导出类型实参
若创建空的list,则需要显示指定类型实参,可以用如下两种方式
val name: MutableList<String> = mutableListOf()
val name2 = mutableListOf<String>()
泛型函数
public fun <T> List<T>.slice(indices: IntRange): List<T> {
if (indices.isEmpty()) return listOf()
return this.subList(indices.start, indices.endInclusive + 1).toList()
}
如对于上面List的slice函数,在使用可不指定类型实参
val letters = ('a'..'z').toList()
println(letters.slice(0..2))
println(letters.slice<Char>(0..2))
泛型类
interface MyList<T> {
operator fun get(index: Int): T
}
类型参数约束
如下指定了T为Number的子类,只能传递数字
fun <T : Number> onHalf(value: T): Double {
return value.toDouble() / 2.0
}
println(onHalf(3))
如果需要多个约束,则使用where
fun <T> addSuffix(seq: T) where T : CharSequence, T : Appendable {
if (!seq.endsWith(".")) {
seq.append('.')
}
}
val s = StringBuilder("Hello")
addSuffix(s)
println(s)
非空类型形参
默认类型形参将使用Any?作为默认上界,即可以传递null作为参数
class Processor<T> {
fun process(value: T) {
value?.hashCode()
}
}
val p = Processor<String?>()
p.process(null)
若要保证类型形参不为空,需要显式使用Any为上界
class Processor<T : Any> {
fun process(value: T) {
value.hashCode()
}
}
运行时的泛型
类型检查和转换
可以用is判断参数是否为List,但无法判断其类型参数,因为会泛型擦除,所以可以省略<>,或者使用<*>
fun printSum(c: Collection<Int>) {
if (c is List<Int>) {
println(c.sum())
}
}
printSum(listOf(1, 2, 3))
fun printSum2(c: Collection<Int>) {
if (c is List<*>) {
println(c.sum())
}
}
fun printSum3(c: Collection<Int>) {
if (c is List) {
println(c.sum())
}
}
而在类型转换时,不能转换特定的类型参数,如下set<Int>转换为List<Int>会类型不匹配,而List<String>转换为List<Int>,实际是擦除后的List,但会在sum()调用时报错
fun printSum4(c: Collection<*>) {
val intList = c as? List<Int> ?: throw IllegalStateException("list is expected")
println(intList.sum())
}
//printSum4(setOf(1, 2, 3)) //IllegalStateException:list is expected
//printSum4(listOf("1", "2", "3")) //java.lang.ClassCastException
声明带实化类型参数的函数(类型参数不被擦除)
如下,正常情况下不能判断类型参数的类型
但在内联函数中的类型参数可以被实化,使用时需要用reified标记类型参数
inline fun <reified T> isA(value: Any) = value is T
println(isA<String>("abc"))
println(isA<Int>(123))
库函数filterIsInstance可用于过滤指定类型参数的元素
val items = listOf("one", 2, "three")
println(items.filterIsInstance<String>())
Kotlin的inline函数可以被Java调用但不能被内联,但带reified的inline函数不能被Java调用(因为其必须内联)
使用实化类型参数代替类引用
如下,在使用需要参数为Class的方法时
var load = ServiceLoader.load(Service::class.java) //等价于Java的Service.class
可以通过实化类型参数来重写
inline fun <reified T> loadService(): ServiceLoader<T>? {
return ServiceLoader.load(T::class.java)
}
var load = loadService<Service>()
实化类型参数的限制
优点:
- 可以用于类型检查和转换(is、!is、as、as?)
- 使用反射API(::class)
- 获取Class (::class.java)
- 作为调用其他函数的类型实参
缺点:
- 不能创建类型参数的类的实例
- 不能调用类型参数类的伴生对象的方法
- 调用带实化参数类型函数的时候不能使用非实化参数类型形参作为类型实参
- 不能把类、属性或者非内联函数的类型参数标记为refied
变型:泛型和子类型化
类、类型和子类型
对于非泛型类,类的名词可以直接当作类型使用,每一个Kotlin类可以构造两种类型,且不可空类型为可空类型的子类型
val s1: String? = null
val s2: String = "A"
fun accept(s: String?) {
}
accept(s1)
accept(s2)
而对于泛型类,需要类型实参替换泛型类的类型形参,每一个泛型类可以构造无数的类型,且它们没有任何关系
val l1: MutableList<Any> = mutableListOf()
val l2: MutableList<Int> = mutableListOf()
fun accept(l: MutableList<Any>) {
}
accept(l1)
//accept(l2)
协变:保留子类型化关系
对于类型参数T,如果用作函数参数在in位置,如果用作返回值在out位置
在类型参数加上out表示协变,可以保留类型参数的子类关系
public interface List<out E> : Collection<E> {
......
}
对于不可变的List,其没有set方法(即不会出现往List<String>存入Int的情况),意味着不会对元素进行改变
val l3: List<Any> = listOf()
val l4: List<Int> = listOf()
fun accept(l: List<Any>) {
}
accept(l3)
accept(l4)
- 构造函数的参数既不在in位置,也不在out位置,即使声明为out也仍可使用
- 构造函数参数使用var,会生成setter,不能使用out标记
- out/in只适用于类外部可见(public、protect和internal)的Api
逆变:反转子类型化关系
对于String的排序方法,接收一个Comparator<String>()
val s = listOf("AB", "C", "DEF")
s.sortedWith(kotlin.Comparator<String>() { s1, s2 ->
s1.length - s2.length
})
println(s)
但它也可以接受一个更普遍的比较器Comparator<Any>()
val s = listOf("AB", "C", "DEF")
s.sortedWith(kotlin.Comparator<Any>() { s1, s2 ->
s1.hashCode() - s2.hashCode()
})
println(s)
因为Comparator的类型参数由in修饰,是逆变的
interface Comparator<in T> {
fun compare(e1: T, e2: T): Int {
}
}
同时存在协变和逆变
对于同时存在in和out的函数,可用(P) -> R表示
interface Funtion1<in P, out R> {
fun invoke(p: P): R
}
如下将Animal作为Cat逆变传参,将返回值Int作为Number协变返回
open class Animal {
open fun getIndex(): Int {
return 0
}
}
class Cat : Animal() {
override fun getIndex(): Int {
return 1
}
}
fun getCatIndex(f: (Cat) -> Number) {
}
getCatIndex(Animal::getIndex)
使用点变型:在类型出现的地方指定变型
上面在定义类和接口使用in和out叫做声明点变型,而在调用函数传递参数时使用in和out叫做使用点变型,如在Java中的List<T>,在写函数时可用使用通配符限制 (? exends 和 ? super)
fun <T> MutableList<T>.copy(source: MutableList<T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}
val intItem = mutableListOf(1, 2, 3)
val anyItem = mutableListOf<Any>()
//copy(intItem, anyItem)
对于上面的copy函数,source只读,destination只写,但实际不能把MutableList<String>拷贝到MutableList<Any>,要实现这个功能,需要引入第二个类型参数,修改为
fun <T : R, R> copy(source: MutableList<T>, destination: MutableList<R>) {
for (item in source) {
destination.add(item)
}
}
val intItem = mutableListOf(1, 2, 3)
val anyItem = mutableListOf<Any>()
copy(intItem, anyItem)
更为简单的方式是使用out,让其在使用点变型,对于只读的source可使用子对象
fun <T> copy(source: MutableList<out T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}
val intItem = mutableListOf(1, 2, 3)
val anyItem = mutableListOf<Any>()
copy(intItem, anyItem)
使用*代替类型参数
- MutableList<*>包含某种特定类型元素的列表,但不知道是哪个类型
- MutableList<Any?>包含任意类型的元素的列表
不能创建MutableList<*>且不可写,但可读,因为所有类型可转为Any?
当类型实参的信息并不重要时,使用星号投影
fun printFirst(list: List<*>) {
if (list.isNotEmpty()) {
println(list.first())
}
}
上面代码也可以通过引入类型参数替换
fun <T> printFirst(list: List<T>) {
if (list.isNotEmpty()) {
println(list.first())
}
}