更多精彩内容,欢迎关注个人微信公众号——Android机动车。数组
不管是Java仍是Kotlin,泛型都是一个很是重要的概念,简单的泛型应用很容易理解,不过也有理解起来麻烦的时候。安全
在了解Kotlin的泛型以前,先来看看Java中的泛型:bash
举个栗子:在JDK中,有一类列表对象,这些对象对应的类都实现了List接口。List中能够保存任何对象:微信
List list=new ArrayList();
list.add(55);
list.add("hello");
复制代码
上面的代码中,List中保存了Integer和String两种类型值。尽管这样作是能够保存任意类型的对象,但每一个列表元素就失去了原来对象的特性,由于在Java中任何类都是Object的子类,这样作的弊端就是原有对象类型的属性和方法都不能再使用了。数据结构
但在定义List时,能够指定元素的数据类型,那么这个List就再也不是通用的了,只能存储一种类型的数据。JDK1.5以后引入了一个新的概念:泛型。函数
所谓泛型,就是指在定义数据结构时,只指定类型的占位符,待到使用该数据结构时再指定具体的数据类型:ui
public class Box<T> {
private T t;
public Box(T t) {
this.t = t;
}
}
Box<Integer> box=new Box(2);
复制代码
在Kotlin中一样也支持泛型,下面是Kotlin实现上面一样的功能:this
class Box<T>(t: T) {
var value = t
}
var box: Box<String> = Box("haha")
复制代码
Java泛型中有类型通配符这一机制,不过在Kotlin泛型中,没有通配符。spa
先看一个Java的栗子:code
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泛型并无提供通配符,取而代之的是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机动车