首发于公众号: DSGtalk1989
10.Kotlin 泛型
-
泛型约束
就是我们在java中看到的
T extends User
,意思就是谁接受的泛型只能是User或者User的子类,在kotlin中我们使用:
来代替fun <T : Number> List<T>.sum():T{ //... }
这里直接将放行和拓展函数连在了一块儿。
kotlin厉害的地方是提供了多个上界,比如说,我现在需要
T
既实现了接口A
,也实现了接口B
,我们无法直接通过T : A
的方式来进行处理,可以使用where
关键字fun <T> List<T>.sum():T where T : A, T : B{ //... }
这个
T
需要同时实现A
和B
。 -
型变in和out
我们先来了解java中的两个概念,协变和逆变。
List<A> aList
和List<B> bList
,A extends B
。我们可以bList.add(new A())
但是我们无法aList.add(new B())
,但是,bList
不能addAll``aList
,List<A>
不是List<B>
的子类,这个大家应该都能理解。有了这一层引导基础,我们结合泛型再来看一下。
List<? extends B> list list.add(new A()) OK list.addAll(List<A>) not OK
我们一般把
? extends
称之为协变。同样的我们也能去理解List<? super B>
,最低我们只能add B
,只能往上不断的往里面扔B
的父类,B
的子类无法进入到这个list
中。我们将? super
称之为逆变。结合
in
和out
,在kotlin中我们怎么去运用这两个操作符,并且跟协变和逆变结合起来。比较容易的记忆方法是,如果我们需要使用返回泛型的,那我们就使用
out
,如果我们需要将泛型放入方法中使用的,我们就是用in
interface Production<out T> { fun produce(): T } interface Consumer<in T> { fun consume(item: T) }
如果过程中我们既要使用返回泛型,又要使用泛型使用,我们就直接使用使用T即可,即既没有
in
也没有out
,这应该是大部分时间会出现的场景。我们继续结合业务场景,进行针对协变逆变和
in``out
做更深度的解读。我们照着上面两个接口的条件,先写下如下代码:
class Banana class BananaFactory : Production<Banana> { override fun produce(): Banana { return Banana() } } var factory : Production<Banana> = BananaFactory()
香蕉工厂,产香蕉。直接单单这么看,不会有很深刻的印象。我们把
banana
换成有依赖关系的三种产品。//食物 open class Food //快餐是食物的一种 open class FastFood : Food() //汉堡是快餐的一种 class Burger : FastFood()
针对这三种产品,有三个相应的工厂
class FoodFactory : Production<Food> { override fun produce(): Food { return Food() } } class FastFoodFactory : Production<FastFood> { override fun produce(): FastFood { return FastFood() } } class BurgerFactory : Production<Burger> { override fun produce(): Burger { return Burger() } }
然后我们针对三种工厂开始进行赋值,首先是显而易见肯定没问题的赋值。
val production1 : Production<Food> = FoodFactory() val production2 : Production<FastFood> = FastFoodFactory() val production3 : Production<Burger> = BurgerFactory()
然后我们尝试这种
Food
去接所有的品类工厂,发现并没有报错。val production1 : Production<Food> = FoodFactory() val production2 : Production<Food> = FastFoodFactory() val production3 : Production<Food> = BurgerFactory()
我们再尝试着用
Burger
去接,发现除了最后一个可以接,上面的统统报错。val production1 : Production<Burger> = FoodFactory() //报错 val production2 : Production<Burger> = FastFoodFactory() //报错 val production3 : Production<Burger> = BurgerFactory()
即
FoodFactory、FastFoodFactory、BurgerFactory
三个工厂生产的都是Food
,但是只有BurgerFactory
生产的肯定是Burger
,FastFoodFactory
生产的可能还有别的快餐,FoodFactory
生产的可能还有别的食物。
我们回头再来看Consumer
的接口的三个实现。
//所有事物都吃
class FoodConsumer : Consumer<Food> {
override fun consume(item: Food) {
}
}
//所有快餐都吃
class FastFoodConsumer : Consumer<FastFood> {
override fun consume(item: FastFood) {
}
}
//所有汉堡都吃
class BurgerConsumer : Consumer<Burger> {
override fun consume(item: Burger) {
}
}
首先,各自对应生成自己的Consumer<?>
肯定是没有问题的,这里我们代码就不写了,我们直接看两个封顶首先是Food
,发现一上来就编译出错了
val consumer1 : Consumer<Food> = FoodConsumer()
val consumer2 : Consumer<Food> = FastFoodConsumer() //报错
val consumer3 : Consumer<Food> = BurgerConsumer() //报错
如何解释,所有食物都吃的人,肯定是吃Food
的。所以第一个没问题。只吃快餐的人FastFoodConsumer
,并不一定吃所有的Food
,只吃汉堡的人BurgerConsumer
就更不用说了。那么可想而知,反过来应该是都通过了。
val consumer1 : Consumer<Burger> = FoodConsumer()
val consumer2 : Consumer<Burger> = FastFoodConsumer()
val consumer3 : Consumer<Burger> = BurgerConsumer()
OK,至此,我们故事讲完了,开始强化理解。
-
我们在没加
in
和out
之前,直接使用泛型T
,效果和在java中的是一样一样的。即针对所有的跟
T
有继承关系的父类或者子类,都无法直接突破,即不变 out
表示子类泛型对象都可以赋值给父类泛型对象,大的包含小的,小的要到大的里面去是out
,out
只返回T
,不消费T
in
表示父类泛型对象都可以赋值给子类泛型对象,大的包含小的,大的要进到小的里面去是in
,in
只消费T
,不返回T
in
和out
是kotlin所独有的,在java中,没有直接申明继承关系的情况下是无法直接进行泛型对象互相赋值的。Array<? extends Food>
直接用Array<out Food>
,即所有Food
的子类都可以添加到这个数组中去,同时所有的Array<Food子类>
都可以赋值给定义为Array<out Food>
的对象-
星号投射
kotlin中,我们用
*
代替所有的类型,相当于Any?
。不用过多的去跟in
、out
、Nothing
等等的做挂钩,只需要知道*
基本就相当于java中的object
,即可以代表任何的类型。举个例子:
class A<T>(val t: T, val t2 : T, val t3 : T) val a1: A<*> = A(12, "String", Apple("苹果"))
在kotlin中,我们一般其实不会想要去申明对象的类型,而交给系统自己去判断,目前的用途还没有特别清楚,待之后补充。
Kotlin学习笔记之 13 基础操作符run、with、let、also、apply