Kotlin范型中的in和out关键字(协变和逆变)

深入学习Kotlin基础知识
2022-06-04 22:21 · 阅读时长7分钟
小课

在使用kotlin的范型时,发现它比java多了两个关键字outin。在java的范型中常用的关键字是extends和super,所以我在想out和in和它们之间是否存在某些关系。关于java范型中的extends和super使用,可以参考Java范型的上限约束和下限约束

要说范型还是得从java的范型说起,我们知道java中的范型在编译之后就没有了,也是就是常提到的类型擦除,比如说下面这段代码。

List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();

在编译之后就变成了这样。

List stringList = new ArrayList();
List integerList = new ArrayList();

既然这样,那为什么还要用范型呢?为了保证类型安全,通过范型约束,我们可以在编译期间提前发现类型安全问题,而不是在运行时报错。比如说,如果没有范型,这段代码是可以编译通过的。

List stringList = new ArrayList();
stringList.add("hello");
Integer element = (Integer) stringList.get(0);

但是在运行时会发生ClassCastException,如果使用了范型进行约束,那么在编译时就会报错,让开发者及时发现错误。

out关键字

但是类型约束也导致了一些问题,比如说我们有一个自定义的列表ZXList,和一个数据类Parent和Child。

class ZXList<T> {
    void addAll(ZXList<T> t) {}
}
class Parent {}
class Child extends Parent {}

现在有一个Parent类型和一个Child类型的ZXList,我们想让两个ZXList合并,很自然地使用addAll方法,但是却发现编译不通过。

ZXList<Parent> parentList = new ZXList<>();
ZXList<Child> childList = new ZXList<>();
parentList.addAll(childList);
加载中...

这就不符合正常逻辑了,按道理Child类型类型应该是能够兼容Parent类型的,但是上面这样addAll定义就是不能兼容,如果需要实现兼容,我们需要extends关键字把addAll方法定义改成这样。

class ZXList<T> {
    void addAll(ZXList<? extends T> t) {}
}

这样的意思是addAll里面的ZXList的范型类型只要是继承自T类型即可。而在kotlin中这些写法就变成了使用out关键字。

class ZXList<T> {
    fun addAll(t: ZXList<out T>) {}
}

以上就是out关键字的作用之一。我们知道按道理childList是兼容parentList的,但是我们却不能把childList赋值给parentList,即下面的java代码编译是无法通过的。

ZXList<Child> childList = new ZXList<>();
ZXList<Parent> parentList = childList;

但是在kotlin中,通过out关键字这也是可以实现的。

class ZXList<out T> { }
val childList = ZXList<Child>()
val parentList: ZXList<Parent> = childList
in关键字

在java的范型中super这个关键字,最经典的应用示例就是List.sort(Comparator<? super E> c),还是以ZXList为例说明,假设ZXList也有一个sort方法,定义如下。

class ZXList<T> {
    void sort(Comparator<T> comparator) { }
}

另外有Animals、Dog和Cat三个类,定义如下。

class Animal {
    protected int age;
}
class Dog extends Animal { }
class Cat extends Animal { }

现在有两个ZXList分别是ZXList<Dog>和ZXList<Cat>,我们想让两个列表都通过age字段进行排序。

Comparator<Animal> comparator = new Comparator<Animal>() {
    @Override
    public int compare(Animal o1, Animal o2) {
        return o1.age - o2.age;
    }
};
ZXList<Dog> dogList = new ZXList<>();
dogList.sort(comparator);
ZXList<Cat> catList = new ZXList<>();
catList.sort(comparator);

运行后发现编译出错,dogList和catList不能使用Comparator<Animal>进行排序,这明显也不符合正常逻辑,Dog和Cat都是继承自Animal,为什么就不能使用Comparator<Animal>排序呢?其实也是有办法的,那就是使用super关键字,把ZXList的sort方法定义改成

class ZXList<T> {
    void sort(Comparator<? super T> comparator) { }
}

在kotlin中要实现这个效果就是使用in关键字,代码如下

class ZXList<T> {
    fun sort(comparator: Comparator<in T>) {}
}
总结

kotlin中的out和in关键字主要用于强化范型的使用,kotlin中的<out T>相当于java中的<? extends T>,而<in T>就相当于<? super T>,但是inout关键字的功能要更强大一些,另外要注意的是java中的范型上限用的也是extends关键字,比如<T extends Animal>,kotlin中范型的上限用的是:,比如T : Animal

kotlin协变逆变outin