众所周知,Java 5才最大的亮点就是引入泛型,那么Java引入泛型的目的是什么?这就须要查看Java 5引入泛型前的代码:(由于Java向后兼容,如今这段代码还能编译成功)html
#daqiJava.java
List list = new ArrayList();
list.add("");
String str = (String) list.get(0);
//添加错误类型
list.add(1);
复制代码
因为ArrayList
底层是依靠Object数组
实现的,这使得任何类型均可以添加到同一个ArrayList
对象中。且取出来时是Object类型,须要强制类型转换后才能进行相应的操做。但因为ArrayList
对象能接受任何类型,没法保证类型转换老是正确的,很容易形成ClassCastException异常。java
但泛型的出现,让这一切都迎刃而解。单个ArrayList
对象只能存储特定类型的对象,若是不是存入该类型或者该类型子类的对象,编译器会报错提醒,规范了ArrayList
中对象的类型。同时,取出来时能够安心的依据泛型的具体类型进行强制类型转换,而且这是在ArrayList
中自动完成强转的,省去了开发者进行强制类型转换带来的繁琐。android
#daqiJava.java
List<String> list = new ArrayList();
list.add("");
String str = list.get(0);
list.add(1);//编译器不经过
复制代码
总的来讲,泛型带来如下好处:数组
类型参数约束能够限制做为泛型类和泛型函数的类型实参的类型。安全
把一个类型指定为泛型的类型形参的上界约束,在泛型类型具体的初始化中,对应的类型实参必须是这个具体类型或它的子类型。bash
换句话说就是,某泛型函数(例如求和函数)能够用在List<Int>
和List<Double>
上,但不能够用在List<String>
上。这时能够指定泛型类型型参的上界为Number
,使类型参数必须使数字。ide
fun <T:Number> sum(num1:T,num2:T):T{
}
复制代码
一旦指定上界,只有 Number
的子类(子类型)能够替代 T。函数
尖括号中只能指定一个上界,若是同一类型参数须要多个上界,须要使用 where-子句:post
fun <T> daqi(list: List<T>)
where T : CharSequence,T : Comparable<T> {
}
复制代码
类型参数约束默认的上界是 Any?
。意味着泛型函数接收的参数可空,尽管泛型T并无标记? 。这时可使用<T : Any>
替换默认上界,确保泛型T永远为非空类型。学习
学习泛型的型变以前,须要先学习本小节的内容,以便更好的理解后面的泛型的型变。在Java中,咱们每每会把类和类型看成相同的概念来使用,但其实它们是两种不一样概念。区分类和类型这两种概念的同时,也须要分状况讨论:
非泛型类的名称能够直接看成类型使用。而在Kotlin中,一个非泛型类至少能够分为两种类型:非空类型和可空类型。例如String
类,能够分为可空类型String?
和 非空类型String
.
而对于泛型类就变得更为复杂了。一个泛型类想获得合法的类型,必须用一个具体的类型做为泛型的类型形参。所以一个泛型类能够衍生出无限数量的类型。例如:Kotlin的List
是一个类,不是一个类型。其合法类型:List<String>
、List<Int>
等。
咱们通常将一个类的派生类称为子类,该类称为父类(基类)。例如:Int
是Number
的派生类,Number
做为父类,Int
做为子类。
而子类型与子类的定义不同,子类型的定义:
任什么时候候指望A类型的值时,可使用B类型的值,则B就是A的子类型
超类型是子类型的反义词。若是B是A的子类型,那么反过来A就是B的超类型。
对于非泛型类,其类型会沿袭该类的继承关系,当A是B的父类,同时A类型也是B类型的超类型。当指望A类型的对象时,可使用B类型的对象进行传递。
全部类的 非空类型 都是该类的 可空类型 的子类型,但反过来不能够。例如:在接收String?
类型的地方,可使用String
类型的值来替换。但不能将String?
类型的值存储到String
类型的值中,由于null
不是非空类型变量能够接收的值。(除非进行判空或非空断言,编译器将可空类型转换为非空类型,这时原可空类型的值能够存储到非空类型的变量中)
做为非泛型类,Int
和String
没有继承关系,二者间不存在子类型或超类型的关系。
咱们都知道非泛型类其类型会沿袭该类的继承关系。但对于泛型类,这是行不通的。例如如下代码,是没法编译成功的:
#daqiJava.java
List<String> strList = new ArrayList();
List<Object> objList = new ArrayList();
objList = strList;
复制代码
List<Object>
和List<String>
是两个相互独立的类型,不存在子类型的关系。即使String
类的基类是Object
类。
由于当你指望List<Object>
时,容许赋值一个List<String>
过来,也就意味着其余的类型(如List<Int>
等)也能赋值进来。这就形成了类型不一致的可能性,没法确保类型安全,违背了泛型引入的初衷 —— 确保类型安全。
到这里你或许会想,对于接收泛型类对象的方法,这不就"削了"泛型类的代码通用性(灵活性)的能力?Java提供了有限制的通配符来确保类型安全,容许泛型类构建相应的子类型化关系,提升代码的通用性(灵活性)。与之对应的,即是Kotlin的型变。Kotlin中存在协变和逆变两种概念,统称为声明处型变。
Kotlin的声明处型变包含了协变和逆变。协变和逆变都是用于规范泛型的类型形参的范围,确保类型安全。
保留子类型化关系
具体意思是:当 B 是 A 的子类型,那么List<B>
就是List<A>
的子类型。协变类保留了泛型的类型形参的子类型化关系。
public fun Out(list: List<out String>) {
}
复制代码
反转子类型化关系
具体意思是:当 B 是 A 的子类型,那么List<A>
就是List<B>
的子类型。逆变类反转了泛型的类型形参的子类型化关系。
public fun In(list: MutableList<in String>) {
}
复制代码
对于协变的定义广泛很容易理解,但对于逆变每每比较费解。因此我决定退一步,借助Java的有限制的通配符进行了解。从官方文档中了解到,协变、逆变和Java的通配符类型参数有如下关系:
通配符类型参数 ? extends A 表示接受 A 或者 A 的子类型。
通配符类型参数 ? super A 表示接受 A 或者 A 的超类型。
因此,out Number
和 in Number
的"取值范围"能够用一张图归纳(暂时只考虑由非泛型类的继承带来的子类型化关系):
Number
类具备Int
、Long
等派生类,同时也拥有Any
这个基类。当须要依据Number
进行协变时(即<out Number>
),泛型的类型形参只能选取Number
自身以及其子类(子类型)。当须要依据Number
进行逆变时(即<in Number>
),泛型的类型形参只能选取Number
自身以及其基类(超类型)。
当某方法中须要List<out Number>
类型的参数时,将<out Number>
转换为<? extends Number>
,表示泛型的类型形参能够为Number
自身以及其子类(子类型)。即List<Number>
协变的子类型集合有:List<Number>
、List<Int>
等。
List<Int>
在List<Number>
协变的子类型集合中。意味着当须要List<Number>
时,可使用List<Int>
来替换,List<Int>
是List<Number>
的子类型。符合协变的要求: Int
是 Number
的子类型,以至List<Int>
也是List<Number>
的子类型。
而若是协变的是List<Int>
,那么将<out Int>
转换为<? extends Int>
。表示泛型的类型形参能够为Int
自身以及其子类(子类型)。即List<Int>
协变的子类型集合只有:List<Int>
。
List<Number>
不在List<Int>
协变的子类型集合中。意味着当须要List<Int>
时,不可使用List<Number>
来替换,List<Number>
不是List<Int>
的子类型。
这种思路对于逆变也是可行的。某方法中须要MutableList<in Number>
类型的参数时,将<in Number>
转换为<? super Number>
,表示泛型的类型形参能够为Number
自身以及其基类(超类型)。即MutableList<Number>
逆变的子类型集合有:MutableList<Number>
、MutableList<Any>
等。
MutableList<Int>
不在MutableList<Number>
逆变的子类型集合中。意味着当须要MutableList<Number>
时,不可使用MutableList<Int>
来替换,MutableList<Int>
不是MutableList<Number>
的子类型。
而若是逆变的是MutableList<Int>
,那么将<in Int>
转换为<? super Int>
。表示泛型的类型形参能够为Int
自身以及其基类(超类型)。即MutableList<Int>
逆变的子类型集合有:MutableList<Int>
、MutableList<Number>
和 MutableList<Any>
。
MutableList<Number>
在MutableList<Int>
逆变的子类型集合中。意味着当须要MutableList<Int>
时,可使用MutableList<Number>
来替换,MutableList<Number>
是MutableList<Int>
的子类型。符合逆变的要求: Int
是 Number
的子类型,但MutableList<Number>
是MutableList<Int>
的子类型。
众所周知,Kotlin中一个非泛型类有着对应的可空类型和非空类型,并且非空类型是可空类型的子类型。由于当须要可空类型的对象时,可使用非空类型的对象来替换。
关于可空类型和非空类型间的协变与逆变,也可使用刚才的方法进行理解,只是此次再也不局限于子类和父类,而是扩展到子类型和超类型。
<out A>
),泛型的类型形参只能选取A自身以及其子类型。<in A>
),泛型的类型形参只能选取A自身以及其超类型。 当某方法中须要List<out Any?>
类型的参数时,将<out Any?>
转换为<? extends Any?>
,表示泛型的类型形参能够为Any?
自身以及其子类型。即List<Any?>
协变的子类型集合有:List<Any?>
、List<Any>
等。
而若是逆变的是MutableList<Any?>
,那么将<in Any?>
转换为<? super Any?>
。表示泛型的类型形参能够为Any?
自身以及其超类型。即MutableList<Any?>
逆变的子类型集合有:MutableList<Any?>
。
当你试图将MutableList<Any>
作为子类型传递给接收MutableList<in Any?>
类型参数的方法时,编译器将报错,编译不经过。由于MutableList<Any?>
逆变的子类型集合中没有MutableList<Any>
。
当某方法中须要List<out Any>
类型的参数时,将<out Any>
转换为<? extends Any>
,表示泛型的类型形参能够为Any
自身以及其子类型。即List<Any>
协变的子类型集合有:List<Any>
。
而若是逆变的是MutableList<Any>
,那么将<in Any>
转换为<? super Any>
。表示泛型的类型形参能够为Any
自身以及其超类型。即MutableList<Any>
逆变的子类型集合有:MutableList<Any>
和 MutableList<Any?>
。
当你试图将List<Any?>
作为子类型传递给接收List<out Any>
类型参数的方法时,编译器将报错,编译不经过。由于List<Any>
协变的子类型集合中没有List<Any?>
。
到这里或许有个疑问,我该依据什么来选择协变或者逆变呢?这就涉及关键字out
和in
的第二层含义了。
关键字out的两层含义:
关键in的两层含义:
out位置是指:该函数生产类型为T
的值,泛型T只能做为函数的返回值。而in位置是指:该函数消费类型T的值,泛型T做为函数的形参类型。
Kotlin的型变听从《Effective Java》中的 PECS (Producer-Extends, Consumer-Super)。只能读取的对象做为生产者,只能写入的对象做为消费者。
out
关键字使得一个类型参数协变:只能够被生产而不能够被消费。 out
修饰符确保类型参数 T 从 Iterator<T>
成员中返回(生产),并从不被消费。
public interface Iterator<out T> {
public operator fun next(): T
public operator fun hasNext(): Boolean
}
复制代码
in
关键字使得一个类型参数逆变:只能够被消费而不能够被生产。 in
修饰符确保类型参数 T
从 Comparable<T>
成员中写入(消费),并从不被生产。
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
复制代码
配合协变分析,能够清楚out
为何扮演生产者角色:
List<Int>
、List<Long>
等子类型能够替代List<Number>
,传递给接收List<Number>
类型的方法。而对外还是List<Number>
,但并不知道该泛型类实际的类型形参是什么。Number
的任何子类型。但因为不知道该泛型类实际的类型形参是什么。对其进行写入会形成类型不安全。(例如:可能接收的是一个List<Int>
,若是你对其写入一个Long
,这时就会形成类型不安全。)List<Int>
,仍是List<Long>
等),返回(生产)的是Number
实例。以超类型的形式返回子类型实例,类型安全。 配合逆变分析,也能够清楚in
为何扮演消费者角色:
Consumer<Number>
、Consumer<Any>
等子类型能够替代Consumer<Number>
,传递给接收Consumer<Number>
类型的方法。Consumer<Number>
(Consumer<Int>
、Consumer<Long>
等不能传递进来)。以超类型的形式消费子类型实例,类型安全。Number
呢,仍是Any
呢?)。只有使用Any
返回(生产)才能确保类型安全,因此读取受限。(也就是说在逆变中,泛型 T
为Number
时,你返回的不是Number
,而是Any
。) 那是否意味着out
关键字修饰的泛型参数是否是不能出如今in
位置 ?固然不是,只要函数内部能保证不会对泛型参数存在写操做的行为,可使用UnSafeVariance
注解使编译器中止警告,就能够将其放在in
位置。out
关键字修饰的泛型参数也是同理。
例如Kotlin的List
中contains
函数等,就是应用UnSafeVariance
注解使泛型参数存在于in位置,其内部没有写操做。
public interface List<out E> : Collection<E> {
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
public operator fun get(index: Int): E
public fun indexOf(element: @UnsafeVariance E): Int
public fun lastIndexOf(element: @UnsafeVariance E): Int
public fun listIterator(): ListIterator<E>
public fun listIterator(index: Int): ListIterator<E>
public fun subList(fromIndex: Int, toIndex: Int): List<E>
}
复制代码
构造方法的参数既不在in
位置也不在out
位置。同时该位置规则只对对外公开的API有效。(即对private
修饰的函数无效)
协变 | 逆变 | 不变 | |
---|---|---|---|
结构 | Producer<out T> |
Consumer<in T> |
MutableList<T> |
Java实现 | Producer<? extends T> |
Consumer<? super T> |
MutableList<T> |
子类型化关系 | 保留子类型化关系 | 逆转子类型化关系 | 无子类型化关系 |
位置 | out位置 | in位置 | in位置和out位置 |
角色 | 生产者 | 消费者 | 生产者和消费者 |
表现 | 只读 | 只写,读取受限 | 便可读也可写 |
那么使用泛型时,逆变、协变和不变如何选择呢?
Array中存在又读又写的操做,若是为其指定协变或逆变,都会形成类型不安全:
class Array<T>(val size: Int) {
fun get(index: Int): T { …… }
fun set(index: Int, value: T) { …… }
}
复制代码
若是须要子类型化关系,则只读操做(协变或不变)选择协变,不然不变;只写读操做(逆变或不变),选择逆变,不然不变。
Kotlin的型变分为 声明处型变 和 星点投射。所谓的星点投射就是使用 * 代替类型参数。表示你不知道关于泛型实参的任何信息,但仍然但愿以安全的方式使用它。
Kotlin 为此提供了如下星点投射的语法:
对于 Foo <T : TUpper>
,其中 T 是一个具备上界 TUpper
的不型变类型参数,Foo<*>
读取值时等价于 Foo<out TUpper>
,而写值时等价于 Foo<in Nothing>
。
对于 Foo <out T : TUpper>
,其中 T 是一个具备上界 TUpper
的协变类型参数,Foo <*>
等价于 Foo <out TUpper>
。 这意味着当 T
未知时,你能够安全地从 Foo <*>
读取 TUpper
的值。
对于 Foo <out T>
,其中 T 是一个协变类型参数,Foo <*>
等价于 Foo <out Any?>
。 由于 T
未知时,只有读取 Any?
类型的元素是安全的。
对于 Foo <in T>
,其中 T
是一个逆变类型参数,Foo <*>
等价于 Foo <in Nothing>
。 由于 T
未知时,没有什么能够以安全的方式写入 Foo <*>
。
对于普通的 Foo <T>
,这其中没有任何泛型实参的信息。Foo<*>
读取值时等价于 Foo<out Any?>
,由于读取 Any?
类型的元素是安全的;Foo<*>
写入值是等价于Foo<in Nothing>
。
若是泛型类型具备多个类型参数,则每一个类型参数均可以单独投影(以interface Function <in T, out U>
为例):
能够向MutableList<Any?>
中添加任何数据,但MutableList<*>只是通配某种类型,由于不知道其具体什么类型,因此不容许向该列表中添加元素,不然会形成类型不安全。