目录
本小结介绍的是kotlin的泛型,先从Java的泛型开始介绍:
1. 泛型
kotlin中使用泛型的方式跟Java相差不大:
class Box<T>(t: T) {
var value = t
}
// 使用
val box: Box<Int> = Box<Int>(1)
//kotlin可以自动推断类型
val box2 = Box(1)
2. 通配符(上下界)
Java
Java中,在没有通配符上下界的情况下,是不可变的,也就是说,在指定泛型类型后,Java就能在编译期确定泛型的类型,防止运行时出现异常。为了泛型的灵活性,提出了通配符上下界。
- 通配符上界,只能从中读取元素,不能添加元素,称为生产者(Producers),用<? extends T>表示。
List<String> strs = new ArrayList<String>();
strs.add("0");
strs.add("1");
List<? extends Object> objs = strs;
objs.get(0); // 可以获取
objs.add(1); // 错误,不能添加
之所以不能添加元素的原因很明显,因为没法确定添加的实例类型跟定义的类型是否匹配。
- 通配符下界,只能添加元素,不能直接读取下界类型的元素,称为消费者(Consumers),用<? super T>表示。
List<String> strs = new ArrayList<String>();
strs.add("0");
strs.add("1");
List<? super String> objs = strs;
objs.add("1");
objs.set(0, "2");
//String s = objs.get(0); get不能直接得到String类型的对象
Object s = objs.get(0);
不得不说,这样做法是合理的,但是是可能会出错的。来看看Kotlin是怎么实现类似的情况的。
kotlin
在Kotlin中,使用声明位置变异来解决这种问题:
- 使用out关键字标识的泛型T,只能作为返回值,而不能作为传入参数。
abstract class Source<out T> {
// 使用out的话,T只能作为返回值
abstract fun nextT(): T
// 不能作为传入参数,下面会报错
// abstract fun add(value: T)
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs
}
这种做法实际上就是对应Java中的<? extends T>,因为只能作为返回值而不能作为参数,所以不会出现Java可能出现的运行异常。
- 使用in关键字标识的泛型T,只能作传入参数,而不能作为返回值。
abstract class Source<in T> {
// 使用in的话,只能作为传入参数,不能作为返回值
// abstract fun nextT(): T
abstract fun add(value: T)
}
fun demo(strs: Source<Number>) {
val objects: Source<Double> = strs // Double是Number的子类型
}
这样是不是更加合理了呢? 用一句概括Kotlin的这种做法,就是:
Consumer in, Producer out!
3. 泛型函数
Kotlin同样支持泛型函数:
fun <T> singletonList(item: T): List<T> {
}
fun <T> T.basicToString() : String { // extension function
}
使用的时候,在函数名称后面指定具体的类型参数:
val l = singletonList<Int>(1)
4. 泛型约束
Kotlin的泛型约束和类的继承一样,使用:代替extends对泛型的的类型上界进行约束:
class SwipeRefreshableView<T : View>{}
同时Kotlin支持多个类型的上界约束,,使用where关键字:
class SwipeRefreshableView<T>
where T : View,
T : Refreshable {
}