java与kotlin的型变(java <? extends> <? super> kotlin out in)

63 阅读4分钟

上界通配符<? extends>

class Employee {
    private String name;

    public Employee(String name) {
        this.name = name;
    }
}

class Manager extends Employee {
    private Integer level;

    public Manager(String name) {
        super(name);
    }
}

class DevManager extends Manager {
    private String language;

    public DevManager(String name) {
        super(name);
    }
}

class WorkStation<T> {
    private T employee;

    public WorkStation(T employee) {
        this.employee = employee;
    }

    public T getEmployee() {
        return employee;
    }

    public void setEmployee(T sam) {
        this.employee = employee;
    }
}
public class MyTest {
    public static void main(String[] args) {
        WorkStation<Manager> managerWorkStation = new WorkStation<Manager>(new Manager("John"));
        WorkStation<? extends Employee> exWorkStation = managerWorkStation;
    }
}

在这个例子中WorkStation被安全的认为是WorkStation的子类型, WorkStation<? extends Employee>表示这个引用的具体类型是Employee或Employee的子类型,而Manager正是Employee的子类型, 所以WorkStation<? extends Employee>可以安全的接受WorkStation

Producer-Extends生产者对应上界通配符extends

如何理解Producer-Extends?举个例子

public class MyTest {
    public static void main(String[] args) {
        WorkStation<Manager> managerWorkStation = new WorkStation<>(new Manager("John"));
        WorkStation<? extends Employee> exWorkStation = managerWorkStation;

        // 只可以获取它和它的基类
        Object a = exWorkStation.getEmployee();
        Employee b = exWorkStation.getEmployee();
        DevManager d = exWorkStation.getEmployee(); // error

        // 不可以存储, 为什么不可以存储? 因为exWorkStation的引用类型为<? extends Employee>,
        // 但其实际类型可能是Employee可能是Employee的任何子类型, 调用任何包含T的函数(如这里的set函数),
        // 编译器无法判断传入的类型是否与真正的实例类型相同, 所以这里提示错误,
        exWorkStation.setEmployee(new Employee("Sam")); // error, incompatible types: Manager cannot be coverted to capture#1 of ? extends Employee
        exWorkStation.setEmployee(new DevManager("James")); // error, incompatible types: DevManager cannot be coverted to capture#1 of ? extends Employee

    }
}

在这个例子中exWorkStation的引用类型为WorkStation<? extends Employee>, 实际类型为WorkStation, 从exWorkStation中取出的数据可以安全的转换为Employee或Employee的父类型因此getEmployee()可以安全的赋值给Object和Employee, 但DevManager是Employee的子类型, 无法从WorkStation<? extends Employee>中取出数据并安全的赋值;

同样的, 由于exWorkStation的引用类型为WorkStation<? extends Employee>但实际类型为WorkStation, 任何以泛型为参数的函数(如这里的set方法), 编译器只知道泛型的具体类型是Employee及其子类型, 不知道其具体类型, 调用set函数时,编译器无法判断传入的类型与具体的实际类型是否匹配,因此不允许使用set函数.具体的,在这个例子中exWorkStation.setEmployee(new Employee("Sam"));试图向exWorkStation中存入Employee对象,但exWorkStation是Manager类型的,父类不允许赋值给子类,编译错误;.exWorkStation.setEmployee(new DevManager("James"));试图向exWorkStation中传入DevManager,虽然在这个例子中exWorkStation的实际类型为Manager, 向其中传入DevManager看似不会出问题,但对于编译器来讲, 编译器只知道exWorkStation是Manager或其子类型, 可能是DevManager也可能是TestManager,如果是TestManager,那么就会出异常,因此这里也不允许直接赋值.

综上所述可以看出来,通过<? extends >修饰的引用类型只允许使用get函数,而不允许使用set函数, 只返回数据, 不接收数据, 因此被称为生产者, 产生数据的类.

协变

WorkStation<? extends Manager> exWorkStation1 = null;
WorkStation<? extends DevManager> exWorkStation2 = null;
exWorkStation1 = exWorkStation2;
exWorkStation2 = exWorkStation1;//error

在这个例子中exWorkStation1的泛型类型为Manager或Manager的子类型, exWorkStation2的泛型类型为DevManager或其子类型, DevManager为Manager的子类型,因此exWorkStation2可以视为exWorkStation1的子类型, 像这种泛型类型与原类型的父子关系保持一致的称为协变; WorkStation<? extends DevManager>是WorkStation<? extends Manager>的子类型,exWorkStation2可以安全的赋值给exWorkStation1,又由于协变常用于输出类型,于是在koltin中定义为out

下界通配符<? super>

public class MyTest2 {
    public static void main(String[] args) {
        WorkStation<? super Manager> supWorkStation = new WorkStation<>(new Manager("James"));

        // 可以存储它和它的子类
        supWorkStation.setEmployee(new DevManager("Sam"));
        supWorkStation.setEmployee(new Manager("Sam"));
        supWorkStation.setEmployee(new Employee("Sam")); // error

        // 只可以获取所有类的基类 - Objectw
        Object o = supWorkStation.getEmployee();
        Employee e = supWorkStation.getEmployee(); // error
        Manager e = supWorkStation.getEmployee(); // error
        DevManager e = supWorkStation.getEmployee(); // error

        // 只能安全强转成它和它的基类
        Employee employee = (Employee) o;
        Manager manager = (Manager) o;

        WorkStation<? super Manager> w = new WorkStation<>(new Manager("Sam"));
        // ClassCastException: Manager cannot be cast to DevManager
        DevManager devManager = (DevManager) w.getEmployee();
    }
}

在这个例子中supWorkStation的引用类型的泛型类型为Manager或其父类,实际类型为Manaer,调用set方法时传入DevManager和Manager, 由于DevManager和Manager都是Manager的子类型,子类型可以安全的转换为父类型,因此可以set,但Employee是Manager的父类型,父类型无法安全的转换为子类型,因此supWorkStation.setEmployee(new Employee("Sam"));编译错误;

同样的,由于supWorkStation的泛型类型为<? super Manager>,因此get取出的数据可能是Manager的任意父类型, 只能安全的赋值给Object对象,但实际上由于set函数的限制,这里是可以安全的进行强制转换为Employee对象和Manager对象

逆变

WorkStation<? super Manager> supWorkStation1 = null;
WorkStation<? super DevManager> supWorkStation2 = null;
supWorkStation2 = supWorkStation1;
supWorkStation1 = supWorkStation2;//error

在这个例子中supWorkStation1的泛型类型为Manager或其父类, supWorkStation2的泛型类型为DevManager或其父类, DevManager又是Manager的子类, 因此WorkStation<? super DevManager>包含了WorkStation<? super Manager>, WorkStation<? super Manager>可以安全的赋值给WorkStation<? super DevManager>,因此WorkStation<? super DevManager>可以视为WorkStation<? super Manager>的父类, 像这种父子类关系与泛型类型相反的情况被称为逆变;又由于其常作为输入参数使用,因此在kotlin中被定义为in;

kotlin out与in

综上所述, kotlin的out为协变, 用作输出类型, 与java的<? extends>相同, 也即上界通配符, in为逆变与java的<? super>相同, 也即下界通配符;

上界通配符作为输出类型即生产者, 于是定义为out 下界通配符作为输入类型即消费者, 于是定义为in