User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Kotlin   ›   Kotlin Notes   ›   正文
Kotlin Notes

Kotlin-泛型—泛型(二十八)

2021-01-31 03:28:51
1294  0 1
参考目录 隐藏
1) 泛型
2) 类型约束
3) java的泛型
4) 泛型的型变
5) Java 上界通配符<? extends T>
6) Java 下界通配符<? super T>
7) Kotlin 与Java型变的比较
8) 不变
9) 协变 out
10) 逆变 in
11) 星投影
12) 泛型的实现类型与内联特化

阅读完需:约 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()
}

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 593721浏览

随机文章
SpringSecurity—捋一遍登录流程( 从源码出发 )
5年前
SpringBoot —构建 RESTful 风格应用
5年前
Caffeine—缓存实战
2年前
Spring—@ControllerAdvice等异常处理方式或统一处理数据
5年前
SpringCloud—OpenFeign(二)参数传递
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 593721 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付