阅读完需:约 15 分钟
泛型
泛型,即 “参数化类型”,将类型参数化,可以用在类,接口,方法上。
与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。

定义泛型:
class Data<T>(val t:T)//泛型类
fun <T> play(i:Int){ //泛型方法
println(i)
}
interface onclick<T>{ //泛型接口
fun click(t:T)
}
val data = Data<String>("hello")//类实现
val p = play<Int>(1) //方法实现
class Data<T>(val t:T):onclick<T>{ //接口实现
override fun click(t: T) {
println(t)
}
}
class Box<T>(t: T) {
var value = t
}
创建类的示例时我们需要指定类型参数:
val box: Box<Int> = Box<Int>(1)
// 或者
val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。
以下示例向泛型类 Box 传入整型数据和字符串:
class Box<T>(t : T) {
var value = t
}
fun main(args: Array<String>) {
var boxInt = Box<Int>(10)
var boxString = Box<String>("Nhooo")
println(boxInt.value)
println(boxString.value)
}
输出结果为:
10
Nhooo
定义泛型类型变量,可以完整地写明类型参数,如果编译器可以自动推定类型参数,也可以省略类型参数。
Kotlin 泛型函数的声明与 Java 相同,类型参数要放在函数名的前面:
fun <T> boxIn(value: T) = Box(value)
// 以下都是合法语句
val box4 = boxIn<Int>(1)
val box5 = boxIn(1) // 编译器会进行类型推断
在调用泛型函数时,如果可以推断出类型参数,可以省略泛型参数。
以下示例创建了泛型函数 doPrintln,函数根据传入的不同类型做相应处理:
fun main(args: Array<String>) {
val age = 23
val name = "nhooo"
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
字符串转换为大写:nhooo
T 不是整型,也不是字符串





sealed class List<T> {
object Nil : List<Nothing>(){
override fun toString(): String {
return "Nil"
}
}
data class Cons<E>(val head: E, val tail: List<E>) : List<E>(){
override fun toString(): String {
return "$head, $tail"
}
}
fun joinToString(sep: Char = ','): String {
return when(this){
Nil -> "Nil"
is Cons -> "${this.head}$sep${this.tail.joinToString(sep)}"
}
}
}
fun main() {
val list = List.Cons(1.0, List.Nil) // 报错 List<Nothing> 不等于 List<E>
}
类型约束
我们可以使用泛型约束来设定一个给定参数允许使用的类型。
Kotlin 中使用 : 对泛型的类型上限进行约束。
最常见的约束是上界(upper bound):


fun <T : Comparable<T>> sort(list: List<T>) {
// ……
}
Comparable 的子类型可以代替 T。 例如:
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型
默认的上界是 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() }
}
例子:
//泛型约束 <占位符:类型>
fun <T:Number> play(vararg param: T):Double{
return param.sumByDouble { it.toDouble() }
}
//多个约束,T有多个上限 , where T:类型,T:类型
fun <T> getBetterBig(list:Array<T>,threhold:T):List<T>
where T:Number,T:Comparable<T>{
return list.filter { it>= threhold }.sorted()
}
类似的还有Map类,它的K 和V泛型都进行了约束:

class Map<K, V> where K : Serializable, V : Comparable<V>
java的泛型


用法和java没什么两样
泛型的型变
声明处的类型变异使用协变注解修饰符:in、out,消费者 in, 生产者 out。
out
相当于 Java 上界通配符。
in
相当于 Java 下界通配符。
Java 上界通配符<? extends T>
如果 Dog 是 Animal 的子类,但 List<Dog> 并不是 List<Animal> 的子类。
下面的代码会在编译时报错:
List<Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
animals = dogs; // incompatible types
而使用上界通配符之后,List<Dog> 变成了 List<? extends Animal> 的子类型。
即 animals 变成了可以放入任何 Animal 及其子类的 List。
因此,下面的代码编译是正确的:
List<? extends Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
animals = dogs;
改成kotlin代码:
fun main() {
var animals: List<Animal> = ArrayList()
val dogs = ArrayList<Dog>()
animals = dogs
}
Java 下界通配符<? super T>
下面的代码因为是协变的,无法添加新的对象。编译器只能知道类型是 Animal 的子类,并不能确定具体类型是什么,因此无法验证类型的安全性。
List<? extends Animal> animals = new ArrayList<>();
animals.add(new Dog()); // compile error
使用下界通配符之后,代码编译通过:
List<? super Animal> animals = new ArrayList<>();
animals.add(new Dog());
? super Animal 表示 Animal 及其父类 。所以 animals 可以接收所有 Animal 的子类添加至该列表中。
Java 的上界通配符和下界通配符符合 PECS 原则。
PECS 原则即 Producer Extends,Consumer Super 。如果参数化类型是一个生产者,则使用 <? extends T>;如果它是一个消费者,则使用 <? super T>。
其中,生产者
表示频繁往外读取数据 T,而不从中添加数据。消费者
表示只往里插入数据 T,而不读取数据。
Kotlin 与Java型变的比较
? 通配符类型
<? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类
<? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object
协变 | 逆变 | 不变 | |
Kotlin | <out T>只能作为消费者,只能读取不能添加。 | <in T>只能作为生产者,只能添加,读取受限制 | <T>既可以添加,也可以读取 |
Java | <? extends T>只能作为消费者,只能读取不能添加。 | <? super T>只能作为生产者,只能添加,读取受限制 | <T>既可以添加,也可以读取 |

不变

这也就是为什么最上面的泛型例子会出错的原因,用协变来解决
协变 out
使用 out 使得一个类型参数协变,协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型:


简单例子:
// 定义一个支持协变的类
class Nhooo<out A>(val a: A) {
fun foo(): A {
return a
}
}
fun main(args: Array<String>) {
var strCo: Nhooo<String> = Nhooo("a")
var anyCo: Nhooo<Any> = Nhooo<Any>("b")
anyCo = strCo
println(anyCo.foo()) // 输出 a
}




例子:假如去书店买书要买教育书籍,然后去了书店,到书店的教育专栏去买教育书籍可以买到,但是去教育书店也可以买到教育书籍,可如果去教育书店买其他书就买不到了。
在这其实你去书店的教育专栏买教育书籍,实际上是将书店强转为了教育书店
例子:
interface Book
interface EduBook : Book
class BookStore<out T : Book> {
fun getBook(): T {
TODO()
}
}
fun covariant(){
val eduBookStore: BookStore<EduBook> = BookStore<EduBook>()
val bookStore: BookStore<Book> = eduBookStore
val book: Book = bookStore.getBook() //书店只能获取普通的书
val eduBook : EduBook = eduBookStore.getBook() //教辅书店只能获取教辅书籍
}
简而言之就是用out
关键字修饰的泛型就是协变,协变有个继承的关系,比如Int是Number的子类,返回值为协变泛型类型的称为协变点。协变点主要是指输出的类型,用来生成某个实例(生产者)。

如果 A 是 B 的子类型,并且Generic<A> 也是 Generic<B> 的子类型,那么 Generic<T> 可以称之为一个协变类。
任何继承父类的对象都可以插入
子类获取子类,父类获取父类,能提供子类的自然可以提供父类。
逆变 in
in 使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型:
简单例子:
// 定义一个支持逆变的类
class Nhooo<in A>(a: A) {
fun foo(a: A) {
}
}
fun main(args: Array<String>) {
var strDCo = Nhooo("a")
var anyDCo = Nhooo<Any>("b")
strDCo = anyDCo
}



open class Waste
class DryWaste : Waste()
class Dustbin<in T : Waste> {
fun put(t: T) {
TODO()
}
}
fun contravariant(){
val dustbin: Dustbin<Waste> = Dustbin<Waste>()
val dryWasteDustbin: Dustbin<DryWaste> = dustbin
val waste = Waste()
val dryWaste = DryWaste()
//普通垃圾桶可以放入任何垃圾
dustbin.put(waste)
dustbin.put(dryWaste)
//干垃圾桶只能放入干垃圾,不能放普通垃圾
// dryWasteDustbin.put(waste)
dryWasteDustbin.put(dryWaste)
}
简而言之就是用in
关键字修饰的泛型就是逆变,作为函数输入参数的泛型称为逆变点。逆变点主要是指输入的参数类型,是消费者。并且消费者的继承关系跟协变是相反的。

例子:
abstract class Printer<in E> {
abstract fun print(value: E): Unit
}
class AnimalPrinter: Printer<Animal>() {
override fun print(animal: Animal) {
println("this is animal")
}
}
class DogPrinter : Printer<Dog>() {
override fun print(dog: Dog) {
println("this is dog")
}
}
fun main() {
val animalPrinter = AnimalPrinter()
animalPrinter.print(Animal())
val dogPrinter = DogPrinter()
dogPrinter.print(Dog())
}
如果 A 是 B 的子类型,并且 Generic<B> 是 Generic<A> 的子类型,那么 Generic<T> 可以称之为一个逆变类。
只要传入的是它的父类就可以
星投影
有些时候, 你可能想表示你并不知道类型参数的任何信息, 但是仍然希望能够安全地使用它. 这里所谓”安全地使用”是指, 对泛型类型定义一个类型投射, 要求这个泛型类型的所有的实体示例, 都是这个投射的子类型。
对于这个问题, Kotlin 提供了一种语法, 称为 星号投射(star-projection):
- 假如类型定义为 Foo<out T> , 其中 T 是一个协变的类型参数, 上界(upper bound)为 TUpper ,Foo<> 等价于 Foo<out TUpper> . 它表示, 当 T 未知时, 你可以安全地从 Foo<> 中 读取TUpper 类型的值.
- 假如类型定义为 Foo<in T> , 其中 T 是一个反向协变的类型参数, Foo<> 等价于 Foo<inNothing> . 它表示, 当 T 未知时, 你不能安全地向 Foo<> 写入 任何东西.
- 假如类型定义为 Foo<T> , 其中 T 是一个协变的类型参数, 上界(upper bound)为 TUpper , 对于读取值的场合, Foo<*> 等价于 Foo<out TUpper> , 对于写入值的场合, 等价于 Foo<in Nothing> .
如果一个泛型类型中存在多个类型参数, 那么每个类型参数都可以单独的投射. 比如, 如果类型定义为interface Function<in T, out U> , 那么可以出现以下几种星号投射:
- Function<*, String> , 代表 Function<in Nothing, String> ;
- Function<Int, *> , 代表 Function<Int, out Any?> ;
- Function<, > , 代表 Function<in Nothing, out Any?> .



星投影在所有逆变点的下限类型是Nothing, 因此不能用在属性或函数上



星号投影用来表明“不知道关于泛型实参的任何信息”。
类似于 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 修饰以及星号投影的类型不确定性,会导致写入的任何值都有可能跟原有的类型冲突。因此,星号投影不能写入,只能读取。
说白了只是一个描述符,可以简写泛型参数而已。
例子:
fun main() {
val queryMap: QueryMap<*, *> = QueryMap<String, Int>()
queryMap.getKey()
queryMap.getValue()
val f: Function<*, *> = Function<Number, Any>()
//f.invoke()
if (f is Function) {
(f as Function<Number, Any>).invoke(1, Any())
}
maxOf(1, 3)
HashMap<String, List<*>>()
//endregion
val hashMap: HashMap<*, *> = HashMap<String, Int>()
//hashMap.get()
}
class QueryMap<out K : CharSequence, out V : Any> {
fun getKey(): K = TODO()
fun getValue(): V = TODO()
}
fun <T : Comparable<T>> maxOf(a: T, b: T): T {
return if (a > b) a else b
}
class Function<in P1, in P2> {
fun invoke(p1: P1, p2: P2) = Unit
}

泛型的实现类型与内联特化




Java与Kotlin实现机制一样,在运行时擦除真正的类型,C#则会真的生成一个类型去执行。





inline fun <reified T> genericMethod(t: T){
//val t = T()
val ts = Array<T>(3) { TODO() }
val jclass = T::class.java
val list = ArrayList<T>()
if(list is List<*>){
println(list.joinToString())
}
}
class Person(val age: Int, val name: String)
inline fun <reified T> Gson.fromJson(json: String): T = fromJson(json, T::class.java)
fun main() {
val gson = Gson()
val person2: Person = gson.fromJson("""{"age":18,"name":"Bennyhuo"}""")
val person3 = gson.fromJson<Person>("""{"age":18,"name":"Bennyhuo"}""")
}
实例:模仿的Self Type
typealias OnConfirm = () -> Unit
typealias OnCancel = () -> Unit
private val EmptyFunction = {}
open class Notification(
val title: String,
val content: String
)
class ConfirmNotification(
title: String,
content: String,
val onConfirm: OnConfirm,
val onCancel: OnCancel
) : Notification(title, content)
interface SelfType<Self> {
val self: Self
get() = this as Self //当前类型强转成Self类型
}
//泛型添加约束只能传子类
open class NotificationBuilder<Self: NotificationBuilder<Self>>: SelfType<Self> {
protected var title: String = ""
protected var content: String = ""
fun title(title: String): Self {
this.title = title
return self //返回接口的常量属性即可,运行时就是当前子类实际类型
}
fun content(content: String): Self {
this.content = content
return self
}
open fun build() = Notification(this.title, this.content)
}
class ConfirmNotificationBuilder : NotificationBuilder<ConfirmNotificationBuilder>() {
private var onConfirm: OnConfirm = EmptyFunction
private var onCancel: OnCancel = EmptyFunction
fun onConfirm(onConfirm: OnConfirm): ConfirmNotificationBuilder {
this.onConfirm = onConfirm
return this
}
fun onCancel(onCancel: OnCancel): ConfirmNotificationBuilder {
this.onCancel = onCancel
return this
}
override fun build() = ConfirmNotification(title, content, onConfirm, onCancel)
}
fun main() {
ConfirmNotificationBuilder()
.title("Hello")
.onCancel {
println("onCancel")
}.content("World")
.onConfirm {
println("onConfirmed")
}
.build()
.onConfirm()
}
如果不定义SelfType类型,则子类在调用ConfirmNotificationBuilder().title(“Hello”)之后不能再继续调用子类的onCancel 方法,因为返回的是父类型,但是实际运行时这个类型是子类型。
实例: 基于泛型实现 Model 实例的注入
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KProperty
abstract class AbsModel {
init {
Models.run { this@AbsModel.register() }
}
}
class DatabaseModel : AbsModel() {
fun query(sql: String): Int = 0
}
class NetworkModel : AbsModel() {
fun get(url: String): String = """{"code": 0}"""
}
class SpModel : AbsModel() {
init {
Models.run { register("SpModel2") }
}
fun hello() = println("HelloWorld")
}
object Models {
private val modelMap = ConcurrentHashMap<String, AbsModel>()
fun AbsModel.register(name: String = this.javaClass.simpleName) {
modelMap[name] = this
}
//String扩展函数
fun <T: AbsModel> String.get(): T {
return modelMap[this] as T
}
}
fun initModels() {
DatabaseModel()
NetworkModel()
SpModel()
}
//object单例
object ModelDelegate {
operator fun <T: AbsModel> getValue(thisRef: Any, property: KProperty<*>): T {
return Models.run {
property.name.capitalize().get() //利用了String的扩展函数
}
}
}
class MainViewModel {
//利用属性代理by by左边指定类型推导泛型
val databaseModel: DatabaseModel by ModelDelegate
val networkModel: NetworkModel by ModelDelegate
val spModel: SpModel by ModelDelegate
val spModel2: SpModel by ModelDelegate
// val compileKotlin: KotlinCompile by tasks //gradle这种写法类似,tasks也是属性代理
// val compileTestKotlin: KotlinCompile by tasks
}
fun main() {
initModels()
val mainViewModel = MainViewModel()
mainViewModel.databaseModel.query("select * from mysql.user").let(::println)
mainViewModel.networkModel.get("https://www.imooc.com").let(::println)
mainViewModel.spModel.hello()
mainViewModel.spModel2.hello()
}