【码上开学】Kotlin 的泛型
在学习kotlin 泛型之前我们先来回顾一下关于Java的泛型基础吧。
说道泛型,我们可能最常用的就是在三大集合中去使用。
泛型
将具体的类型泛化,编码的时候用符号来值代类型,在使用时再确定他的类型。
因为泛型的存在,我们可以省去强制类型转化。
泛型是跟类型相关的,那么是不是也能使用与类型的多态呢?
场景一:
代码语言:javascript复制//多态,因为Button是TextView的子类,向上转型
TextView textView=new Button(context);
List<Button> buttons=new ArrayList<Button>();
//当我们将多态使用到这里时,就发生错误。
List<TextView> textViews=buttons;
为什么List<TextView> textViews=buttons;
会报错呢?这是因为Java的泛型本身
具有不可变性。Java里面会认为List<TextView>
和List<Button>
类型不一致,
也就是说,子类的泛型(List<Button>
)不属于泛型(List<TextView>
)的子类。
Java的泛型类型会在编译时发生类型擦除,为了保证类型安全,不允许这样赋值、 至于什么是类型擦除,等下再讲。
在实际使用中,我们的确会用这种类似的需求,需要实现上面这种赋值。
Java也已经想到了,所以为我们提供了泛型通配符 ? exntends
与? super
来解决这个问题
正确认识Java泛型中? exntends
与? super
? exntends
代码语言:javascript复制 List<Button> buttons=new ArrayList<Button>();
List<? extends TextView> textViews=buttons;
这个 ? extends
叫做 上界通配符
,让Java泛型具有协变性,协变就是允许上面
的赋值是合法的。
它有两层意思:
- 其中
?
是一个通配符,表示这个List
的泛型类型是一个未知类型 extends
限制了这个未知类型的上界,也就是泛型类型必须满足这个extends
的 限制条件
这里和定义class
的extends
关键字有点不一样:
- 它的范围不仅仅是所有直接或者间接子类,还包括上界定义的父类本身,也就是
TextView
- 它还有
implements
的意思,即这里的上界也可以是interface
.
这里的Button
是TextView
的子类,所以满足泛型类型的限制条件,因而能够成功
赋值。
以下几种情况也可以赋值成功.
代码语言:javascript复制List<? extends TextView> textViews=new ArrayList<TextView>(); //本身
List<? extends TextView> textViews=new ArrayList<Button>(); //直接子类
List<? extends TextView> textViews=new ArrayList<RadioButton>(); //间接子类
一般的集合类包含了get
和add
的两种操作,比如Java
中的List
。
public interface List<E> extends Collection<E>{
E get(int index);
boolean add(E e);
}
上面代码中的 E
表示泛型类型的符号。
我们看看在使用上界通配符后,List
的使用上有没有什么问题:
List<? extends TextView> textViews=new ArrayList<Button>();
TextView textView=textViews.get(0);//get方法可以使用
textViews.add(textView);//add会报错
前面说到 List<? extends TextView> 的泛型类型是个未知类型 ?,编译器也不确 定它是啥类型,只是有个限制条件。
由于它满足 ? extends TextView
的限制条件,所以get
出来的对象,肯定是TextView
的子类。根据多态的特性,能够赋值给TextView
。
到了add
操作时,我们可以理解为:
List<? extends TextView>
由于类型未知,它可能是List,也可能是List<TextView>
、List<RadioButton>
。- 对于前者,显然我们要添加
TextView
是不可以的 - 实际情况是编译器无法确定到底属于那一种。无法继续执行下去,就报错了。
你可能在想那么我为什么使用通配符?
呢?
其实,List<?>
相当于List<? extends Object>
的缩写。
由于 add 的这个限制,使用了 ? extends
泛型通配符的 List
,只能够向外提供数据被消费,从这个角度来讲,向外提供数据的一方称为「生产者 Producer」。对应的还有一个概念叫「消费者 Consumer」,对应 Java 里面另一个泛型通配符 ? super。
? super
代码语言:javascript复制List<? super Button> buttons=new ArrayList<TextView>()
这个? super
叫做下界通配符,可以使java
泛型具有逆变性
有两层含义:
- 通配符
?
表示List
的泛型类型是一个未知类型 super
限制了这个未知类型的下界,也就是这个泛型类型必须满足这个super
限制条件super
我们在类的方法里面经常用到,这里的范围不仅包括Button
的直接和间接 父类,也包括Button
本身super
同样支持interface
。
根据上面的例子,TextView
是Button
的父类行,也就能满足super
的限制条件,
就可以成功赋值了。
List<? super Button> buttons=new ArrayList<Button>(); //本身
List<? super Button> buttons=new ArrayList<TextView>(); //直接父类
List<? super Button> buttons=new ArrayList<Object>(); //间接父类
对于使用了下界通配符的List
,我们在看看get
和add
操作:
List<? super Button> buttons=new ArrayList<TextView>();
Object object=buttons.get(0); //此处get()可以获取,是因为Object是所有类的父类
Button button =new Button();
buttons.add(button)
解释下,首先 ? 表示未知类型,编译器是不确定它的类型的。
虽然不知道它的具体类型,不过在 Java 里任何对象都是 Object 的子类,所以这里能把它赋值给 Object。
Button 对象一定是这个未知类型的子类型,根据多态的特性,这里通过 add 添加 Button 对象是合法的。
使用下界通配符 ? super 的泛型 List,只能读取到 Object 对象,一般没有什么实际的使用场景, 通常也只拿它来添加数据,也就是消费已有的 List<? super Button>,往里面添加 Button, 因此这种泛型类型声明称之为「消费者 Consumer」。
小结下,Java 的泛型本身是不支持协变和逆变的。
可以使用泛型通配符 ? extends
来使泛型支持协变,但是「只能读取不能修改」,
这里的修改仅指对泛型集合添加元素,如果是 remove(int index)
以及 clear
当然是可以的。
可以使用泛型通配符? super
来使泛型支持逆变,但是「只能修改不能读取」,
这里说的不能读取是指不能按照泛型类型读取,你如果按照 Object
读出来再强转当然也是可以的。
说完了Java的泛型之后,我们在回头看一下kotlin中的泛型。
kotlin 中的out
和in
kotlin
和java
泛型一样,kotlin
中的泛型本身也是不可变的。
-使用关键字out
来支持协变,等同于Java
中的上界通配符? extends
-使用关键字in
来支持逆变,等同于Java
中的上界通配符? super
var textViews:List<out TextView>
var textViews:List<in TextView>
out
表示,我这个变量或者参数只能用来输出,不用来输入,你只能读我,不能写我;
in
表示:我只用来输入,不用输出,你只能写我,不能读我。
out
代码语言:javascript复制interface Book
interface EduBook:Book
class BookStore<out T:Book>{
//此处的返回类型,则为协变点
fun getBook():T{
TODO()
}
}
fun main() {
val eduBookStore:BookStore<EduBook> = BookStore<EduBook>()
val bookStore:BookStore<Book> =eduBookStore
//我需要书,不管什么类型的,只要是书即可。所以下列两种均可满足
val book:Book=bookStore.getBook()
val book1:Book=eduBookStore.getBook()
var book2:EduBook = eduBookStore.getBook()
//此处错误,因为已经指定了需要Edu类型的书,你却将book给我。引发错误
var book4:EduBook = bookStore.getBook()
}
out
小节:
- 子类
Derived
兼容父类Base
- 生产者
Producer<Derived>
兼容Producer<Base>
- 存在协变点的类的泛型参数必须声明为协变或者不变
- 当泛型类作为泛型参数类实例的生产者时用协变
in
代码语言:javascript复制//垃圾
open class Waste
//干垃圾
class DryWaste : Waste(){}
//垃圾桶
class Dustbin <in T:Waste>{
fun put(t:T){
TODO()
}
}
fun demo(){
//创建一个垃圾桶
val dustbin:Dustbin<Waste> =Dustbin<Waste>()
//创建一个干垃圾桶
val dryWasteDustbin:Dustbin<DryWaste> = dustbin
//垃圾对象
val waste=Waste()
//干垃圾对象
val dryWaste=DryWaste()
//我们的垃圾桶,可以装我们的垃圾,以及干垃圾
dustbin.put(waste)
dustbin.put(dryWaste)
//而干垃圾桶,只能装干垃圾,所以下面这句话,是错误的。
dryWasteDustbin.put(waste)
dryWasteDustbin.put(dryWaste)
}
in
小节:
- 子类
Derived
兼容父类Base
- 消费者
Producer<Derived>
兼容Producer<Base>
- 存在逆变点的类的泛型参数必须声明为协变或者不变
- 当泛型类作为泛型参数类实例的消费者时用协变
*
号
*
号
前面讲到了 Java 中单个?
号也能作为泛型通配符使用,相当于 ? extends Object
。
它在 Kotlin 中有等效的写法:*
号,相当于out Any
。
var list: List<*>
和 Java 不同的地方是,如果你的类型定义里已经有了out
或者 in
,
那这个限制在变量声明时也依然在,不会被*
号去掉。
比如你的类型定义里是out T : Number
的,那它加上 <*>
之后的效果就不是 out Any
,
而是 out Number
。
例子: 协变点例子
代码语言:javascript复制class QueryMap<out K:CharSequence,out V:Any> {
fun getKey():K =TODO()
fun getValue():V =TODO()
}
val queryMap:QuerMap<*,*>= QueryMap<String,Int>()
queryMap.getKey()//类型为CharSequence
queryMap.getValue()//类型为Any
逆变点例子
代码语言:javascript复制class Function<in P1,in P2>{
fun invoke(p1: P1,p2: P2)=TODO()
}
val f:Function<*,*>=Function<Number,Any>()
f.invoke()//参数为下限,但是我们的kotlin中下限为`Nothing`,无法实例化。所以该方法的参数是传入不了的
*
规则
- 如果使用在
out
修饰的类的泛型中使用,那么就会取其上限 - 如果使用在
in
修饰的类的泛型中使用,那么就会取其下限Nothing
*
使用范围
-
*
不能直接或者间接应用在属性或者函数上- 错误方式:
QueryMap<String,*>()
- maxOf<*>(1,3)
-
*
适用用于作为类型描述的场景val querMap:QueryMap<*,*>
if(f is Function<*,*>){...}
HashMap<String,List<*>>()
,注意:此处的List<*>,实际是value
的泛型参数
泛型的概念
1.泛型是一种类型层面的抽象
2.泛型通过泛型参数实现构造更加通用的类型能力
3.泛型可以让符合继承关系的类型批量实现某些能力
泛型类
代码语言:javascript复制 class List<T> {}
泛型方法
代码语言:javascript复制 fun <T> maxOf(a:T,b:T):T
泛型约束
代码语言:javascript复制//表示 T 是Comparable的实现类
fun <T : Comparable<T>> maxOf(a:T,b:T):T{
return if(a>b) a else b
}
多个泛型约束(where
)
代码语言:javascript复制//表示 T 是Comparable的实现类,并且是一个返回值为Unit的方法
fun <T> callMax(a:T,b:T) where T:Comparable<T>,T:() ->Unit{
if (a>b) a() else b()
}
多个泛型参数
代码语言:javascript复制 //该函数返回类型R必须继承Number, T 必须实现Comparable 接口,并且是一个返回类型为R的方法
fun <T,R> callMax(a:T,b:T):R
where T:Comparable<T>,T:() -> R,R:Number{
return if (a>b) a() else b()
}
内联特化
在讲解内联特化时我们需要知道泛型的原理。
- 伪泛型:编译时擦除类型,运行时无实际类型生成
- 例如:
java
、kotlin
- 例如:
- 真泛型:编译时生成真实类型,运行时也存在该类
- 例如:
C#
、C
- 例如:
我们知道JVM上的泛型,一般是通过类型擦除来实现的,所以也被成为伪泛型,也就说类型实参在运行时是不保存的。
实际上,我们可以声明一个inline函数,使其类型实参不被擦除,但是这在Java中是不行的。
代码语言:javascript复制 inline fun <reified T> getericMethod(t:T){
//此处编译器报错,因为我们即使知道了这个T是什么类型,但不清楚,T有没有无参构造器
val t=T()
val ts=Array<T>(3){ TODO()}
val jclass=T::class.java
val list=ArrayList<T>()
}
实际应用
代码语言:javascript复制Gson中的
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
//我们可以简写为
inline fun <reified T> Gson.fromJson(json:String):T =fromJson(json,T::class.java)
//使用时
val person:Person=gson.fromJson(""" {...}""") //通过类型推导
val person=gson.fromJson<Person>(""" {...}""") //泛型参数