范型
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) {
}
}