Kotlin 泛型 | 02. 高阶 - 型变

三、泛型型变:协变、逆变与不变

3.1 协变

3.1.1 基本定义

        如果在定义的泛型类、接口和泛型方法的泛型参数前面加上 out 关键词,说明这个泛型类、接口和泛型方法是协变

        也就是说,A 是 B 的子类,那么 List<A> 也是 List<B> 的子类。

class Demo {
    interface Producer<out T> { // 在泛型类型形参前面指定 out 修饰符
        val something: T
        fun produce(): T
    }
}

        那么,协变如何解决类型不安全的问题?——只能读,不能写;只能作为方法返回值或修饰只读权限的属性,不能作为方法参数类型或可变权限的属性。

3.1.2 关键内容

  • out;
  • 生产者;
  • 只能读,不能写;
  • 对应 Java 中 Collection<? extends Object>
  • 只能作为方法返回值或修饰只读权限的属性,不能作为方法参数类型;

        注:Kotlin 中的 List 并不是 Java 中的 List,因为 Kotlin 中的 List 是个只读的 List,不具备修改集合中元素的操作方法。Java 中的 List 实际上相当于 Kotlin 中的 MutableList,它具有各种读和写的操作方法。

3.2 逆变

3.2.1 基本定义

        如果在定义的泛型类、接口和泛型方法的泛型参数前面加上 in 关键词,说明这个泛型类、接口和泛型方法是逆变

        也就是说,A 是 B 的子类,那么 List<B> 反过来是 List<A> 的子类。

class Demo {
    interface Consumer<in T> { // 在泛型类型形参前面指定 in 修饰符
        fun consume(value: T)
    }
}

        那么,逆变如何解决类型不安全的问题?——只能写,不能读;只能作为方法的形参类型或修饰可变权限的属性。

3.2.2 关键内容

  • in;
  • 消费者;
  • 只能写,不能读;
  • 对应 Java 中 List<? super String>
  • 只能作为方法的形参类型或修饰可变权限的属性;

3.2.3 加深理解的Demo参考如下:

正常情况下:

class Demo {
    private val doubleList = mutableListOf(2.0, 3.0)
    private val intList = mutableListOf(2, 3)

    private val doubleComparable = Comparator<Double> { d1, d2 -> // 一个 Double 类型比较器
        d1.compareTo(d2)
    }
   
    private val intComparable = Comparator<Int> { i1, i2 -> // 一个 Int 类型比较器
        i1.compareTo(i2)
    }

    fun test() {
        doubleList.sortWith(doubleComparable)
        intList.sortWith(intComparable)
    }
}

使用逆变后:

class Demo {
    private val doubleList = mutableListOf(2.0, 3.0)
    private val intList = mutableListOf(2, 3)

    private val numberComparable = Comparator<Number> { n1, n2 -> // 一个 Number 父类型比较器
        n1.toDouble().compareTo(n2.toDouble())
    }

    fun test() {
        doubleList.sortWith(numberComparable)
        intList.sortWith(numberComparable)
    }
}

3.3 不变

        除去协变和逆变就是不变了,它就是我们常用的普通泛型,它既没有 in 关键字修饰,也没有 out 关键字修饰。

class Demo {
    interface MutableList<E> { // 没有 in 和 out 修饰
        fun add(element: E) // E可以作为函数形参类型处于逆变点,输入消费 E
        fun subList(fromIndex: Int, toIndex: Int): MutableList<E> // E又可以作为函数返回值类型处于协变点,生产输出 E
    }
}

3.4 生产者、消费者的概念

        Joshua Bloch 称那些你只能从中读取的对象为生产者,并称那些你只能写入的对象为消费者。他建议:“为了灵活性最大化,在表示生产者或消费者的输入参数上使用通配符类型”,并提出了以下助记符:

        PECS 代表生产者-Extends、消费者-Super(Producer-Extends, Consumer-Super)。

注意:如果你使用一个生产者对象,如 List<? extends Foo>,在该对象上不允许调用 add() 或 set()。但这并不意味着该对象是不可变的:例如,没有什么阻止你调用 clear()从列表中删除所有元素,因为 clear() 根本无需任何参数。通配符(或其他类型的型变)保证的唯一的事情是类型安全。不可变性完全是另一回事。

3.5 Kotlin与Java的型变比较

不变协变逆变
kotlin实现方式:<T>,可读可写实现方式:<out T>,只能读不能写,生产者实现方式:<in T>,只能写不能读,消费者
Java实现方式:<T>,可读可写实现方式:<? extends T>,只能读不能写,生产者实现方式:<? super T>,只能写不能读,消费者

自创顺口溜:

泛型本是不变,Kotlin让它型变;

协变加逆变,就是没有裂变。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值