kotlin-泛型

208 阅读6分钟

一、泛型是什么

泛型的由来

现在的程序开发大都是面向对象的,平时会用到各种类型的对象,一组对象通常需要用集合来存储它们,因而就有了一些集合类,比如 List、Map 等。

这些集合类里面都是装的具体类型的对象,如果每个类型都去实现诸如 TextViewList、ActivityList 这样的具体的类型,显然是不可能的。

因此就诞生了「泛型」,它的意思是把具体的类型泛化,编码的时候用符号来指代类型,在使用的时候,再确定它的类型。

List<TextView> textViews = new ArrayList<TextView>();

泛型不可变性

☕️
TextView textView = new Button(context);
// 👆 这是多态

List<Button> buttons = new ArrayList<Button>();
List<TextView> textViews = buttons;
// 👆 多态用在这里会报错 incompatible types: List<Button> cannot be converted to List<TextView>

因为 Java 的泛型本身具有「不可变性 Invariance」,Java 里面认为 List 和 List 类型并不一致,也就是说,子类的泛型(List)不属于泛型(List)的子类。

Java 提供了「泛型通配符」 ? extends 和 ? super 来解决这个问题。

泛型通配符

Java 中的 ? extends

在 Java 里面是这么解决的:

☕️
List<Button> buttons = new ArrayList<Button>();
      👇
List<? extends TextView> textViews = buttons;

这个 ? extends 叫做「上界通配符」,可以使 Java 泛型具有「协变性 Covariance」,协变就是允许上面的赋值是合法的。

它有两层意思:

  1. 其中 ? 是个通配符,表示这个 List 的泛型类型是一个未知类型。
  2. extends 限制了这个未知类型的上界。它的范围不仅是所有直接和间接子类,还包括上界定义的父类本身,也就是 TextView。

用代码表示

List<? extends TextView> textViews = new ArrayList<TextView>(); // 👈 本身
List<? extends TextView> textViews = new ArrayList<Button>(); // 👈 直接子类
List<? extends TextView> textViews = new ArrayList<RadioButton>(); // 👈 间接子类

get、add

一般集合类都包含了 get 和 add 两种操作,比如 Java 中的 List,它的具体定义如下:

public interface List<E> extends Collection<E>{
    E get(int index);
    boolean add(E e);
    ...
}

上面的代码中,E 就是表示泛型类型的符号(用其他字母甚至单词都可以)。

我们看看在使用了上界通配符之后,List 的使用上有没有什么问题:

List<? extends TextView> textViews = new ArrayList<Button>();
TextView textView = textViews.get(0); // 👈 get 可以
textViews.add(textView);
//             👆 add 会报错,no suitable method found for add(TextView)

由于它满足 ? extends TextView 的限制条件,所以 get 出来的对象,肯定是 TextView 的子类型,根据多态的特性,能够赋值给 TextView,啰嗦一句,赋值给 View 也是没问题的。

到了 add 操作的时候,我们可以这么理解:

List<? extends TextView> 由于类型未知,它可能是 List

,也可能是 List。 对于前者,显然我们要添加 TextView 是不可以的。

由于 add 的这个限制,使用了 ? extends 泛型通配符的 List,只能够向外提供数据被消费,从这个角度来讲,向外提供数据的一方称为「生产者 Producer」。对应的还有一个概念叫「消费者 Consumer」

Java 中的 ? super

List<? super Button> buttons = new ArrayList<TextView>();

这个 ? super 叫做「下界通配符」,可以使 Java 泛型具有「逆变性 Contravariance」。

它也有两层意思:

  1. 通配符 ? 表示 List 的泛型类型是一个未知类型。
  2. super 限制了这个未知类型的下界.这里的范围不仅包括 Button 的直接和间接父类,也包括下界 Button 本身。
List<? super Button> buttons = new ArrayList<Button>(); // 👈 本身
List<? super Button> buttons = new ArrayList<TextView>(); // 👈 直接父类
List<? super Button> buttons = new ArrayList<Object>(); // 👈 间接父类

get、add

List<? super Button> buttons = new ArrayList<TextView>();
Object object = buttons.get(0); // 👈 get 出来的是 Object 类型
Button button = ...
buttons.add(button); // 👈 add 操作是可以的

虽然不知道它的具体类型,不过在 Java 里任何对象都是 Object 的子类,所以这里能把它赋值给 Object。

Button 对象一定是这个未知类型的子类型,根据多态的特性,这里通过 add 添加 Button 对象是合法的。

使用下界通配符 ? super 的泛型 List,只能读取到 Object 对象,一般没有什么实际的使用场景,通常也只拿它来添加数据,也就是消费已有的 List<? super Button>,往里面添加 Button,因此这种泛型类型声明称之为「消费者 Consumer」。

小结

小结下,Java 的泛型本身是不支持协变和逆变的。

  • 可以使用泛型通配符 ? extends 来使泛型支持协变,但是「只能读取不能修改」,这里的修改仅指对泛型集合添加元素,如果是 remove(int index) 以及 clear 当然是可以的。
  • 可以使用泛型通配符 ? super 来使泛型支持逆变,但是「只能修改不能读取」,这里说的不能读取是指不能按照泛型类型读取,你如果按照 Object 读出来再强转当然也是可以的。

Kotlin 中的 out 和 in

和 Java 泛型一样,Kolin 中的泛型本身也是不可变的。

  • 使用关键字 out 来支持协变,等同于 Java 中的上界通配符 ? extends。
  • 使用关键字 in 来支持逆变,等同于 Java 中的下界通配符 ? super。

out 表示,我这个变量或者参数只用来输出,只能作为返回不能作为入参;in 就反过来,表示它只用来输入,只能作为入参,不能作为返回。

class Sample<out T>{
    private val t:T? = null

//    fun setName(t:T){//报错 不能作为入参
//      //
//    }

    fun getName():T?{
        return t
    }
}

声明处的 out 和 in

在前面的例子中,在声明 Producer 的时候已经确定了泛型 T 只会作为输出来用,但是每次都需要在使用的时候加上 out TextView 来支持协变,写起来很麻烦。

Kotlin 提供了另外一种写法:可以在声明类的时候,给泛型符号加上 out 关键字,表明泛型参数 T 只会用来输出,在使用的时候就不用额外加 out 了。

class Producer<out T> {
    fun produce(): T {
        ...
    }
}

val producer: Producer<TextView> = Producer<Button>() // 👈 这里不写 out 也不会报错
val producer: Producer<out TextView> = Producer<Button>() // 👈 out 可以但没必要

与 out 一样,可以在声明类的时候,给泛型参数加上 in 关键字,来表明这个泛型参数 T 只用来输入。

* 号

前面讲到了 Java 中单个 ? 号也能作为泛型通配符使用,相当于 ? extends Object。

它在 Kotlin 中有等效的写法:* 号,相当于 out Any。

var list: List<*>

where 关键字

设置多个边界可以使用 where 关键字:

class Monster<T> where T : Animal, T : Food

reified 关键字

由于 Java 中的泛型存在类型擦除的情况,任何在运行时需要知道泛型确切类型信息的操作都没法用了。

比如你不能检查一个对象是否为泛型类型 T 的实例:

<T> void printIfTypeMatch(Object item) {
    if (item instanceof T) { // 👈 IDE 会提示错误,illegal generic type for instanceof
        System.out.println(item);
    }
}

Kotlin 里同样也不行:

fun <T> printIfTypeMatch(item: Any) {
    if (item is T) { // 👈 IDE 会提示错误,Cannot check for instance of erased type: T
        println(item)
    }
}

这个问题,在 Java 中的解决方案通常是额外传递一个 Class 类型的参数,然后通过 Class#isInstance 方法来检查:

<T> void check(Object item, Class<T> type) {
    if (type.isInstance(item)) {
               👆
        System.out.println(item);
    }
}
inline fun <reified T> printIfTypeMatch(item: Any) {
    if (item is T) { // 👈 这里就不会在提示错误了
        println(item)
    }
}

参考资料

原文 rengwuxian.com/kotlin-gene…

out in blog.csdn.net/u013488580/…