Home Java和Kotlin的范型
Post
Cancel

Java和Kotlin的范型

范型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,为了兼容旧Java版本,所以允许时会被擦除。

举例

目前有如下几个关系的类

范型示例

Java
1
2
3
class ArrayList<E>{
    public boolean add(E e) {}
}

在Kotlin中也是类似的,对于范型,还可以设置上界,例如:

Java

1
public class PersonList<T extends Person> extends ArrayList<T> { }

Kotlin

1
class PersonList<T : Person> : ArrayList<T>()

这样就很好的限制了使用PersonList的范型必须为Person或子类

协变和逆变

因为即使Student是Person的子类,但是在Java和Kotlin中,PersonList<Person>PersonList<Student>是并没有关系的,所以也不能相互转换,要解决这个问题,所以引入了协变和逆变两个概念,大致关系如下图

协变

Java

协变代表正常的继承关系,在Java中为<? extends Person>,kotlin中为<in Person>,即PersonList<Student>也可以正常的转化成PersonList<Person>,例如

1
2
3
4
5
6
7
8
9
ArrayList<Student> studentList = new ArrayList<Student>();

// 子类可以赋值给父类
ArrayList<? extends Person> personList = studentList;
// 不可以再添加元素
// personList.add(new Person());

// 可以正常获取到目前这个类型
Person person = personList.get(0);

因为转化为ArrayList<? extends Person>类型后,若再添加元素,添加的元素并不能保证满足原始ArrayList<Student>类型。使用是危险操作,不被允许。

Kotlin

kotlin也是支持和Java一样的协变的,在kotlin中操作符为out,但在此基础上,kotlin的out修饰在Class定义时,还会禁止入参使用范型,而只允许出参带范型。例如:

1
2
3
4
5
6
7
8
9
10
11
12
class CacheData<out T : Person>(private var data: T) {
    fun get(): T = data
}

fun test(){
    val student = CacheData<Student>(Student())
    val student1 = student.get()
    // 可以赋值给父类
    val person: CacheData<Person> = student
    // 并且可以获取元素
    val person1 = person.get()
}

在类定义时直接带有out参数后,生成的类直接就可以支持协变。

逆变

逆变算是反向的继承关系,所以在Java中使用super关键字表示,在Kotlin中用in表示,表示这个子类的引用可以接收父类的实例。例如

Java

1
2
3
4
5
6
7
8
9
ArrayList<Person> personList = new ArrayList<Person>();

// 父类list可以转化给子类的list
ArrayList<? super Student> studentList = personList;

// 可以往内部正常的进行添加
studentList.add(new Student());
// 不再支持获取
// Person person = studentList.get(0);

和前面的类似,因为ArrayList<Person>赋值给ArrayList<? super Student>后,因为ArrayList<Person>中元素并不能保证是Student,所以当作是Student来获取肯定是会异常,所以获取操作是被禁止的。

Kotlin

在kotiln中的逆变对应in,也同样可以修饰在类定义上,会直接限制方法的定义,禁止掉返回值为对应范型的方法。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 设置逆变关系
class CacheData<in T : Person>() {
    private var data: T? = null
    fun set(t: T) {
        data = t
    }
}

fun test(){
    val person = CacheData<Person>()
    person.set(Student())
    // 可以赋给子类对象
    val student : CacheData<Student> = person
    // 依然可以正常添加
    student.add(Student())
}

因为父类对象赋值给了子类对象,所以设置参数依然可以保证加入的元素是Person类型的,设置对应in,所以in也代表逆变器。而这里CacheData<Person>时添加的元素并不能保证是满足为Student的,所以是不能进行取操作的。

@UnsafeVariance

因为对比Java,kotlin的范型限制更加的严格,在有些时候out修饰的class仍需要有范型参数作为输入参数,这是就需要使用@UnsafeVariance来保证能够编译通过,而保证运行时的问题则需要靠自己保证了。例如:

1
2
3
4
5
6
7
8
9
class AL<out T : Person>(
    private var data: T
) {
    fun get(): T = data

    fun set(t: @UnsafeVariance T) {

    }
}
This post is licensed under CC BY 4.0 by the author.