更多精彩内容,欢迎关注我的微信公众号——Android机动车。
无论是Java还是Kotlin,泛型都是一个非常重要的概念,简单的泛型应用很容易理解,不过也有理解起来麻烦的时候。
泛型基础
在了解Kotlin的泛型之前,先来看看Java中的泛型:
举个栗子:在JDK中,有一类列表对象,这些对象对应的类都实现了List接口。List中可以保存任何对象:
List list=new ArrayList();
list.add(55);
list.add("hello");
上面的代码中,List中保存了Integer和String两种类型值。尽管这样做是可以保存任意类型的对象,但每个列表元素就失去了原来对象的特性,因为在Java中任何类都是Object的子类,这样做的弊端就是原有对象类型的属性和方法都不能再使用了。
但在定义List时,可以指定元素的数据类型,那么这个List就不再是通用的了,只能存储一种类型的数据。JDK1.5之后引入了一个新的概念:泛型。
所谓泛型,就是指在定义数据结构时,只指定类型的占位符,待到使用该数据结构时再指定具体的数据类型:
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")
类型变异
Java中
Java泛型中有类型通配符这一机制,不过在Kotlin泛型中,没有通配符。
先看一个Java的栗子:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1; // 编译错误
以上代码编译错误。这里有两个List对象,很明显String是Object的子类,但遗憾的是,Java编译器并不认为List < String >和List < Object> 有任何关系,直接将list1赋值给list2是会编译报错的,这是由于List的父接口是Collection:
public interface Collection<E> extends Iterable<E> {..}
为了解决这个问题,Java泛型提供了问号(?)通配符来解决这个问题。例如Collection接口中的addAll方法定义如下:
boolean addAll(Collection<? extends E> var1);
? extends E 表示什么呢,表示任何父类是E(或者E的任何子类和自己)都满足条件,这样就解决了List < String > 给List < Object> 赋值的问题。
出了extend还有super,这里不再过多介绍。
Kotlin中
Kotlin泛型并没有提供通配符,取而代之的是out和in关键字。用out声明的泛型占位符只能在获取泛型类型值得地方,如函数的返回值。用in声明的泛型占位符只能在设置泛型类型值的地方,如函数的参数。
我们习惯将只能读取的对象称为生产者,将只能设置的对象称为消费者。如果你使用一个生产者对象,将无法对这个对象调用add或set等方法,但这并不代表这个对象的值是不变的。例如,你完全可以调用clear方法来删除List中的所有元素,因为clear方法不需要任何参数。
通配符类型(或者其他任何的类型变异),唯一能够确保的仅仅是类型安全。
abstract class Source<out T> {
abstract fun func(): T
}
abstract class Comparable<in T> {
abstract fun func(t: T)
}
类型投射
如果将泛型类型T声明为out,就可以将其子类化(List < String > 是List < Object> 的子类型),这是非常方便的。如果我们的类能够仅仅只返回T类型的值,那么的确可以将其子类化。但如果在声明泛型时未使用out声明T呢?
现在有一个Array类如下:
class Array<T>(val size: Int) {
fun get(index: Int): T {
}
fun set(index: Int, t: T) {
}
}
此类中的T既是get方法的返回值,又是set方法的参数,也就是说Array类既是T的生产者,也是T的消费者,这样的类就无法进行子类化。
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for(i in from.indices){
to[i]=from[i]
}
}
这个copy方法,就是将一个Array复制到另一个Array中,现在尝试使用一下:
val ints: Array<Int> = arrayOf(1, 2, 3)
val any: Array<Any> = Array(3)
copy(ints, any) // 编译错误,因为Array<Int> 不是Array<Any>的子类型
Array< T > 对于类型参数T是不可变的,因此Array< Int> 和Array< Any>他们没有任何关系,为什么呢?因为copy可能会进行一些不安全的操作,也就是说,这个函数可能会试图向from中写入数据,这样可能会抛类型转换异常。
可以这样:
fun copy(from: Array<out Any>, to: Array<Any>) {
...
}
将from的泛型使用out修饰。
这种声明在Kotlin中称为类型投射:from不是一个单纯的数组,而是一个被限制(投射)的数组,我们只能对这个数组调用那些返回值为类型参数T的函数,在这个例子中,我们只能调用get方法,这就是我们事先使用处的类型变异的方案。
in关键字也是同理。
泛型函数
不仅类可以有泛型参数,函数一样可以有泛型参数。泛型参数放在函数名称之前。
fun <T> getList(item: T): List<T> {
...
}
调用泛型函数时,应该在函数名称之后指定调用端类型参数。
val value = getList<Int>(1)
泛型约束
对于一个给定的泛型参数,所允许使用的类型,可以通过泛型约束来限制,最常见的约束是上界,与Java中的extends类似。
fun <T : Any> sort(list: List<T>) {
}
冒号之后指定的类型就是泛型参数的上界:对于泛型参数T,允许使用Any的子类型。如果没有指定,则默认使用的上界类型是“Any?”,在定义泛型参数的尖括号内,值允许定义唯一一个上界。
小结
Kotlin泛型是在Java泛型的基础上进行了改进,变得更好用,更安全,尽管上述的泛型技术不一定都用得上,但对于全面了解Kotlin泛型会起到很大作用。
更多精彩内容,欢迎关注我的微信公众号——Android机动车
网友评论