Kotlin基础——泛型

泛型类型参数

编译器一般可以推导出类型实参

若创建空的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())
    }
}
相关推荐
花海少爷2 分钟前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
手握风云-3 分钟前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
喵叔哟22 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生28 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
hopetomorrow42 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
小牛itbull1 小时前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i1 小时前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
闲暇部落1 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
GIS瞧葩菜1 小时前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
chnming19871 小时前
STL关联式容器之set
开发语言·c++