kotlin泛型型变

227 阅读4分钟

泛型

泛型,就是指在定义数据结构时,只指定类型的占位符,待到使用该数据结构时再指定具体的数据类型:

public class Box<T> {
    
    private T t;

    public Box(T t) {
        this.t = t;
    }
}

Box<Integer> box=new Box(2);

在Kotlin中同样也支持泛型,下面是Kotlin实现上面同样的功能:

class Box<T>(t: T) {
    var value = t
}

var box: Box<String> = Box("haha")

Kotlin 泛型函数的声明与 Java 相同,类型参数要放在函数名的前面:

private <T> void aa(T m){

}
   fun <T> doPrintln(content: T) {
        when (content) {
            is Int -> println("整型数字为 $content")
            is String -> println("字符串转换为大写:${content.toUpperCase()}")
            else -> println("T 不是整型,也不是字符串")
        }
    }

泛型型变

1.Java中的逆变协变

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void toWork() {
        System.out.println("我是工人"+getName()+",我要好好干活!!!");
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}

新建一个Person类

public class Worker1 extends Person {

    public Worker1(String name, int age) {
        super(name, age);
    }

    @Override
    public void toWork() {
        System.out.println("我是1工人"+getName()+",我要好好干活!!!");
    } 
}

Worker1继承自Person类,并重写了toWork()方法。

public class Worker2 extends Person {

    public Worker2(String name, int age) {
        super(name, age);
    }

    @Override
    public void toWork() {
        System.out.println("我是2工人"+getName()+",我也要好好干活!!!");
    }
}
(1)协变
List<Person> personList = new ArrayList<>();
personList.add(new Person("aaa",11));
personList.add(new Worker1("bbb",12));
personList.add(new Worker2("ccc",13));

因为Worker1和Worker2都是Person的子类,所以上面这段代码这么写是可以的。

public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("aaa",11));
        personList.add(new Worker1("bbb",12));
        personList.add(new Worker2("ccc",13));

        List<Worker1> worker1List = new ArrayList<>();
        worker1List.add(new Worker1("ddd",14));
        
        List<Worker2> worker2List = new ArrayList<>();
        worker2List.add(new Worker2("eee",15));

        setWork(personList);
        setWork(worker1List);
        setWork(worker2List);
    }

    public static void setWork(List<Person> studentList) {
        for (Person o : studentList) {
            if (o != null){
                o.toWork();
            }
        }
    }

此时会发现,报错了,Worker1和Worker2是Person的子类,但是List《Worker1》和List《Worker2》并不是List《Person》的子类,Java编译器并不认为List《Person》和List《Worker1》或List《Worker2》有任何关系。

那要怎么解决呢?

即Java中的协变:

  public static void setWork(List<? extends Person> studentList) {
        for (Person o : studentList) {
            if (o != null){
                o.toWork();
            }
        }
    }

ps:协变的泛型只能获取但不能修改了,比如在List中就只能get而不能add了 ,用的时候一定要注意: 为什么呢?

因为咱们刚才把泛型改成了协变的,意思就是上界咱们定为了Person,但是咱们的类型写的是 ? 编译器并不知道咱们给它的具体类型是什么,只要是继承自Person的类就可以,所以get出的对象肯定是Person的子类型,根据多态的特性,所以能够直接赋值给Person,但是add就不可以了,咱们可能添加的是List或List,还有可能是List,所以编译器无法确定咱们添加的到底是什么类型就无法继续执行了,肯定就报错了。

(2)逆变

逆变和协变是正好相反的。

List<Person> personList = new ArrayList<>();
personList.add(new Person("aaa",11));
personList.add(new Worker1("bbb",12));
personList.add(new Worker2("ccc",13));

List<Worker1> worker1List = new ArrayList<>();
worker1List.add(new Worker1("ddd",14));

setWorker(personList);
setWorker(worker1List);

public static void setWorker(List<Worker1> studentList) {
     for (Object o : studentList) {
        System.out.println("哈哈 "+o.toString());
     }
}            

会报错,方法接收的是List《Worker1》,但是传的却有List《Person》,而且 Woker1是Person的子类,而不是Person是Worker1的子类。

那么这种情况下就可以使用逆变了:

public static void setWorker(List<? super Worker1> studentList) {
    for (Object o : studentList) {
        System.out.println("哈哈 "+o.toString());
    }
}

逆变和协变一样,类型也是 ?,不过 ?extends 是上界通配符,而 ?super 是下界通配符,它的范围包括Worker1和它的父类。和协变相反,逆变中是可以add的,因为Worker1一定是这个未知类型的子类型,所以是可以add的(即可以接收所有 Worker1的子类添加至该列表中。)。这里也没有get的限制,会变成Object ,因为在Java中所有类型都是Object的子类。

比如我们再新建一个Worker1的子类Worker3:

public class Worker3 extends Worker1{
    public Worker3(String name, int age) {
        super(name, age);
    }
    @Override
    public void toWork() {
        System.out.println("我是3工人"+getName()+",我是Worker1的子类!");
    }
}

那么逆变就可以add Worker1及其子类Worker3了:

 public static void setWorker(List<? super Worker1> studentList) {
        studentList.add(new Worker1("ddd",15));
        studentList.add(new Worker3("ddd",15));
        for (Object o : studentList) {
            System.out.println("哈哈 "+o.toString());
        }
    }

2.kotlin中的逆变协变

Kotlin的逆变和协变和Java中的类似,Java泛型中有类型通配符这一机制,不过在Kotlin泛型中,没有通配符,取而代之的是out和in关键字。

(1)kotlind的协变-out

kotlin版的Person,Worker1,Worker2

open class Person(var name: String, var age: Int) {
    open fun toWork() {
        println("我是工人$name,我要好好干活!!!")
    }
}
class Worker1(name: String, age: Int) : Person(name, age) {
    override fun toWork() {
        println("我是1工人$name,我要好好干活!!!")
    }
}
class Worker2(name: String, age: Int) : Person(name, age) {
    override fun toWork() {
        println("我是2工人$name,我也要好好干活!!!")
    }
}

协变:

fun main() {
    val personArrayList: MutableList<Person> = ArrayList()
    personArrayList.add(Person("aaa", 11))
    personArrayList.add(Worker1("bbb", 12))
    personArrayList.add(Worker2("ccc", 13))

    val personArrayList1: MutableList<Worker1> = ArrayList()
    personArrayList1.add(Worker1("ddd", 14))
    val personArrayList2: MutableList<Worker2> = ArrayList()
    personArrayList2.add(Worker2("eee", 15))
    setWork(personArrayList)
    setWork(personArrayList1)
    setWork(personArrayList2)
}

fun setWork(studentList: List<out Person>) {
    for (o in studentList) {
        o.toWork()
    }
}

仔细看下setWork()这个方法,这个方法提醒咱们这里的out关键字可以省略掉。 因为Kotlin中List是只读的,所以说肯定是安全的,所以官方在定义List接口的时候就直接定义成了协变的:

(2)kotlin的逆变-in
  val personArrayList: MutableList<Person> = ArrayList()
  personArrayList.add(Person("aaa", 11))
  personArrayList.add(Worker1("bbb", 12))
  personArrayList.add(Worker2("ccc", 13))

  val personArrayList1: MutableList<Worker1> = ArrayList()
  personArrayList1.add(Worker1("ddd", 14))

  setWorker(personArrayList)
  setWorker(personArrayList1)
fun setWorker(studentList: MutableList<in Worker1>) {
    for (o in studentList) {
         println("哈哈 " + o.toString())
    }   
}
fun setWorker(studentList: MutableList<in Worker1>) {
    studentList.add(Worker1("ddd", 15))

    for (o in studentList) {
         println("哈哈 " + o.toString())
    }   
}

总结: kotlin的协变逆变同Java的类似,java中有通配符机制 ?extends T(包括T和它的子类)和?super T(包括T和它的父类);但kotlin中是通过out,in关键字实现。协变中不可以add,逆变中可以add T及其子类。