本文章已授权微信公众号郭霖(guolin_blog)转载。
本文章讲解的内容是泛型的型变,我写一个扩展Boolean的示例代码来应用我要讲的内容,示例代码如下:
先看下以下例子,代码如下:
List<String> strings = new ArrayList<String>();
// Java中禁止这样的操作
List<Object> objects = strings;
在Java中是禁止这样的操作的,我们看下Kotlin的写法,代码如下:
val strings: List<String> = arrayListOf()
val anys: List<Any> = strings
在Kotlin中是允许这样的操作的,这是为什么呢?下面会详细解释。
在List中,List是基础类型,String是类型实参,现有两个List集合,分别是List和List,它们都具有相同的基础类型,但是类型实参不相同,并且String和Any存在父子关系,型变就是指**List和List**这两者存在什么关系。
形式参数和实际参数
函数中的形参和实参
代码如下:
fun add(firstNumber: Int, secondNumber: Int): Int =
firstNumber + secondNumber
firstNumber和secondNumber就是形式参数,然后去调用这个函数,代码如下:
val first = 1
val second = 2
add(first, second)
first和second就是add函数的实际参数。
泛型中的形参和实参
代码如下:
class Fruit<T>(var item: T)
T就是类型形参,然后使用这个类,代码如下:
val fruit = Fruit<Int>(100)
Int就是Fruit的类型实参,因为Kotlin具有类型推导特性,不必明确指明类型,所以其实可以写成如下代码:
val fruit = Fruit(100)
在这种情况下,Int依然是Fruit的类型实参。
还有以下情况,请看代码:
// Collections.kt
public interface MutableList<E> : List<E>, MutableCollection<E> {
// 省略部分代码
}
这里的E是List和MutableCollection的类型实参,同时是MutableList的类型形参。
结论
定义在里面就是形式参数,定义在外面就是实际参数。
子类、超类、子类型、超类型
子类会继承超类,例如class Apple: Fruit(),Apple就是Fruit的子类,Fruit就是Apple的超类,那什么是子类型和超类型呢?它们的规则比子类和超类更加宽松,如果需要A类型的地方,都可以用B类型来代替,那么B类型就是A类型的子类型,A类型就是B类型的超类型,例如String和String?,如果一个函数接收的是String?,我们传入的是String的话,编译器是不会报错的,但是如果一个函数接受的是String,我们传入的是String?的话,编译器就会提示我们可能会存在空指针的问题,所以String就是**String?**的子类型,String?就是String的超类型。
子类型化关系
如果需要A类型的地方,都可以用B类型来代替,那么B类型就是A类型的子类型,B类型到A类型之间的映射关系就是子类型化关系,举个例子:List是List的子类型,所以List到List之间存在子类型化关系,List是List<String?>的子类型,所以List到List<String?>之间存在子类型化关系,**MutableList和MutableList**之间就没有关系,这个会在下面解释。
协变
协变(convariant)就是保留子类型化关系,保证泛型内部操作该类型时是只读的,在Java中,带extends限定(上界)的通配符类型使得类型是协变的。
因为List是协变,String是Any的子类型,String是String?的子类型,所以List是List的子类型,List是List<String?>的子类型。
out协变点
以下代码是标准的out协变点:
// T被声明为out
interface Producer<out T> {
// T作为只读属性的类型
val value: T
// T作为函数返回值的类型
fun produce(): T
// T作为只