理解一下super, extends in Java 和 逆变协变(in, out) in Kotlin

720 阅读3分钟

最早遇到super和extends的时候没有注意他们的区别,因为也一直没用到过。最近在学习Kotlin的时候遇到了逆变、协变(in\out)的概念,决定好好研究一下。

是什么

先拿代码试一下

Kotlin版本

open class Person
open class User: Person()
open class Reader: User()
// Person > User > Reader

fun main() {
    val readerList = mutableListOf<Reader>()
    val userList = mutableListOf<User>()
    val personList = mutableListOf<Person>()
    
    addInUser(readerList) //报错
    addInUser(userList)
    addInUser(personList)

    addOutUser(readerList)
    addOutUser(userList)
    addOutUser(personList) //报错
}

fun addInUser(readerList: MutableList<in User>) {
    readerList.add(Person())  //报错 Type mismatch Required User Found Person
    readerList.add(User())
    readerList.add(Reader())
    mutableListOf<Person>().addAll(readerList) // 报错 Type mismatch Required Collection<Person> Found MutableList<in User>
    mutableListOf<User>().addAll(readerList) // 报错
    mutableListOf<Reader>().addAll(readerList) // 报错
}

fun addOutUser(readerList: MutableList<out User>) {
    readerList.add(Person()) //报错
    readerList.add(User()) //报错
    readerList.add(Reader()) //报错
    mutableListOf<Person>().addAll(readerList)
    mutableListOf<User>().addAll(readerList)
    mutableListOf<Reader>().addAll(readerList) //报错
}

Java版本

public class Test {
    public static class Person {}
    public static class User extends Person {}
    public static class Reader extends User {}
    
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        List<User> userList = new ArrayList<>();
        List<Reader> readerList = new ArrayList<>();

        addSuperUser(personList);
        addSuperUser(userList);
        addSuperUser(readerList); //报错

        addExtendsUser(personList); //报错
        addExtendsUser(userList);
        addExtendsUser(readerList);
    }

    static void addSuperUser(List<? super User> list) {
        list.add(new Person()); // 报错
        list.add(new User());
        list.add(new Reader());
        new ArrayList<Person>().addAll(list); // 报错
        new ArrayList<User>().addAll(list); // 报错
        new ArrayList<Reader>().addAll(list); // 报错
    }

    static void addExtendsUser(List<? extends User> list) {
        list.add(new Person()); // 报错
        list.add(new User()); // 报错
        list.add(new Reader()); // 报错
        new ArrayList<Person>().addAll(list);
        new ArrayList<User>().addAll(list);
        new ArrayList<Reader>().addAll(list); // 报错
    }
}

从代码是否报错可以看出superin是类似的,extendsout是类似的。那么这两组关键字分别代表什么呢?

在main方法中,我们分别将三种list传给addInUser/addSuperUseraddOutUser/addExtendsUser

addInUser/addSuperUser只可以接收personListuserList addOutUser/addExtendsUser只可以接收readerListuserList

也就是说

  • 规定参数是in或super时,只能传这个类的List本身或者其父类的List;
  • 规定参数是out或extends时,只能传这个类的List本身或者其子类的List。

为什么要这么规定?

来个高大上的:

Type covariance: This means that the container types have the same relationship to each other as the payload types do. This is expressed using the extends keyword.

Type contravariance: This means that the container types have the inverse relationship to each other as the payload types. This is expressed using the super keyword.

我看完也一头雾水。我粗暴的意译下就是covariance(协变)是说容器的类型是可以被荷载的参数的类型"搞"的。contravariance(逆变)是说容器的类型要"搞"荷载的参数类型的。

List<? extends User> 可以被所有User及其子类的容器add

List<? super User> 可以add所有User及其子类的容器

其实就是那句著名的

Producer Extends,Consumer Super

如果你是生产者,提供数据的,被add的,或是别人从你这取数据的,使用Extends,放低你的姿态,让别人能add到你,拿到你的数据

如果你是消费者,取数据的,要add别人的,取别人数据的,使用Super,抬高位置,好能add到别人

in和out也同理