Kotlin泛型

学习目标:

学习泛型,应用场景,学以致用


学习内容:

1.泛型的基本概念

2.泛型约束

3.泛型的形变

4.UnsafeVariance

5.星投影Start Projection

6.泛型的实原理与内联特化


学习时间:

时间就像海绵里的水,挤一挤总会有的


学习开始:

第一节:泛型的基本概念

1.泛型的概念

  • 泛型是一种类型层面的抽象
  • 泛型通过泛型参数实现构造更加通用的类型的能力
  • 泛型可以让符合继承关系的类型批量实现某些能力
//函数生命泛型
fun <T>maxOf(a:T , b:T):T {
    return a
}
   //类声明泛型
    class List<T>

 泛型参数 分为 形参 和 实参

生明的时候 是 形参 

泛型参数实例化的 是后是实参


第二节:泛型的约束

第一节中 maxof 无法比较字符串大小 ,这时我们可以使用泛型约束

让 泛型参数 继承自 Comparable 来实现字符串的比较


 

fun main() {
    val max = maxOf("Hello","World")
    println(max)
}

fun <T:Comparable<T>>maxOf(a:T , b:T) :T{
   return if (a>b) a else b
}

  我们在泛型中 不仅可以 约束 ,还可以实现多个约束 使用 where 语句

多个泛型参数

在java中定义多个约束 使用的是 交叉类型 a&b

//多个约束
fun <T> callMax(a :T , b:T) where T:Comparable<T> , T:() -> Unit{
    if (a >b) a() else b()
}

//多个泛型参数
fun <T , R> callMax(a:T,b:T):R where T:Comparable<T> , T:() -> R{
   return  if (a >b) a() else b()
}

//多个泛型参数 可以添加多个约束
fun <T , R> callMax(a:T,b:T):R where T:Comparable<T> , T:() -> R ,R:Number{
    return  if (a >b) a() else b()
}

 


第三节:泛型的形变

泛型实参的继承关系对泛型类型的继承关系的影响

 

1.形变

协变 :教辅书店可以买到 教辅书籍和书 ,书店可以买书 不一定买到教辅书籍

教辅书籍 是书集的子类 

open class A
class B : A()

fun main() {
    // Kotlin 中的泛型是默认不型变的,无法自动完成类型转换
    val array1: Array<B> = arrayOf(B(), B(), B())
    val array2: Array<A> = array1 //这行代码 会报错
}

B继承子A 

A是B的父类

Array<A> 就是 Array<B> 的父类

理论上 Array<A> = Array<B> 是合法的 安全的

Koltin中 泛型是默认不形变的 ,无法自动完成类型转换


 val list1: List<B> = listOf(B(), B(), B())
 val list2: List<A> = list1

list是可以的 我们 看源码分析一下

public class Array<T> {}

public interface List<out E> : Collection<E> {}

通过源码可以得知 List泛型参数中使用了out修饰符 

这就是kotlin中用来像编译器解释这种情况

关于out修饰符我们可这样理解,当类、接口的泛型类型参数被声明为out时,则该类型参数是协变的,泛型类型的子类型是被保留的,它只能出现在函数的输出位置,只能作为返回类型,即生产者。带来的好处是,AB的父类,那么List<A>可以是List<B>的父类

下面我们通过书店和 教辅书店的 案例 来学习

package com.junbao

//定义书的接口
interface Book

//定义教辅书的接口
interface EduBook :Book


//定义书店类
//BookStore 接收一个 协变的泛型参数  参数T约束是Book 所以一定是 Book类型的
class BookStore<out T:Book>{
    //T 是协变点 返回 T
    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()
    //子类 可以拿 父类的 父类不能拿子类的
    val book1:Book= eduBookStore.getBook()
}
  • 子类Derived兼容父类Base
  • 生产者Producer<Deriverd>兼容Producer<Base>
  • 存在协变点的类的泛型参数必须声明为协变或者不变
  • 当泛型类作为泛型参数类实例的生产者时用协变


2.逆变

val list1: List<A> = listOf(A(), A(), A())
val list2: List<B> = list1

 

反过来 因为 List<B> 不是 List<A>的父类 所以 上面代码错

这时候 我们 可以通过in修饰符 来解决这一问题 

逆变就是 和形变相反 

关于in修饰符我们可这样理解,当类、接口的泛型类型参数被声明为in时,则该类型参数是逆变的,泛型类型的父类型是被保留的,它只能出现在函数的输入位置,作为参数,只能作为消费类型,即消费者

package com.junbao

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)
}

逆变

  • 子类Derived兼容父类Base
  • 消费者Consumer<Base> 兼容 Consumer<Derived>
  • 存在逆变点的类的泛型参数必须声明为逆变或不变
  • 当泛型类做为泛型参数类实例的消费者时用逆变


第四节:UnSaveVariance 

 协变里面 出现了 逆变点 是不合法的 违反了原则

sealed class List<out T>{
    //@UnsafeVariance 通过它 来修饰这块是安全的 自己来保证
    operator fun contains(t:@UnsafeVariance T):Boolean{
        return false
    }
}

可以通过@UnsafeVariance 来保证这块是安全的 ,编译器 可以不用管

违反形变约束是不安全的

违反形变约束的安全前提:

  • 即声明的协变的类出现逆变点或相反
  • 声明为不变的类接收逆变或协变的类型参数

安全前提:

  • 泛型参数协变,逆变点不能引起修改,即始终只读不写
  • 泛型参数逆变,协变点不得外部获取,即始终只写不读

 

 

第五节:星投影

‘*’可用在变量类型声明的位置

‘*’可用以描述一个未知的类型

‘*’所替换的类型在:

        ·协变点返回泛型参数上限类型

        ·逆变点接收泛型参数下限类型


协变点

//协变点
class QueryMap<out K:CharSequence , out V:Any>{
    fun getKey():K = TODO()
    fun getValue():V = TODO()
}

fun main() {
    val queryMap: QueryMap<*,*> = QueryMap<String , Int>()
    queryMap.getKey()  //上限 CharSequence
    queryMap.getValue() //上限 Any
}

使用的位置 使用在类型声明的位置 不能使用在后面


逆变点

class Function<in P1 , in P2>{
    fun invoke(p1:P1 ,p2:P2) =Unit
}


val f:Function<*,*> = Function<Number , Any>()
f.invoke() //Nothing

逆变点取下线

kotlin中定义泛型是没有办法加下线的约束的

所以对于所有类型他的下线只有一种Nothing


上面代码的invoke没有办法调用

因为Nothing没有实例

星投影的适用范围

“*”不能直接或者间接应用在属性或函数上

        

  •                 QueryMap<String , *>()    间接作用到属性上
  •                 maxOf<*>(1,3)   直接作用到函数上

星投影可以用来描述一个类型

  • val queryMap: QueryMap<*,*>
  •   if(f is Function<*,*>){...}  
  •    HashMap<String , List<*>>()      

第六节:泛型实现原理与内联特化

下回补充吧

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值