Kotlin学习笔记之 10 泛型

作者: super_shanks | 来源:发表于2019-03-27 14:57 被阅读11次

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需要同时实现AB

  • 型变in和out

    我们先来了解java中的两个概念,协变和逆变。

    List<A> aListList<B> bListA extends B。我们可以bList.add(new A()) 但是我们无法aList.add(new B()),但是,bList不能addAll``aListList<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称之为逆变。

    结合inout,在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生产的肯定是BurgerFastFoodFactory生产的可能还有别的快餐,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,至此,我们故事讲完了,开始强化理解。

  • 我们在没加inout之前,直接使用泛型T,效果和在java中的是一样一样的。

    即针对所有的跟T有继承关系的父类或者子类,都无法直接突破,即不变

  • out表示子类泛型对象都可以赋值给父类泛型对象,大的包含小的,小的要到大的里面去是outout只返回T,不消费T

  • in表示父类泛型对象都可以赋值给子类泛型对象,大的包含小的,大的要进到小的里面去是inin只消费T,不返回T

  • inout是kotlin所独有的,在java中,没有直接申明继承关系的情况下是无法直接进行泛型对象互相赋值的。

  • Array<? extends Food>直接用Array<out Food>,即所有Food的子类都可以添加到这个数组中去,同时所有的Array<Food子类>都可以赋值给定义为Array<out Food>的对象

  • 星号投射

    kotlin中,我们用*代替所有的类型,相当于Any?。不用过多的去跟inoutNothing等等的做挂钩,只需要知道*基本就相当于java中的object,即可以代表任何的类型。

    举个例子:

    class A<T>(val t: T, val t2 : T, val t3 : T)
    val a1: A<*> = A(12, "String", Apple("苹果"))
    

    在kotlin中,我们一般其实不会想要去申明对象的类型,而交给系统自己去判断,目前的用途还没有特别清楚,待之后补充。

相关文章

  • Kotlin for android学习六:泛型

    前言 kotlin官网和kotlin教程学习教程的笔记。 1. 声明泛型 2. 泛型约束 : 对泛型的类型上限进行...

  • Kotlin学习笔记之 10 泛型

    10.Kotlin 泛型 泛型约束就是我们在java中看到的T extends User,意思就是谁接受的泛型只能...

  • Kotlin学习笔记(10)- 泛型

    系列文章全部为本人的学习笔记,若有任何不妥之处,随时欢迎拍砖指正。如果你觉得我的文章对你有用,欢迎关注我,我们一起...

  • Kotlin学习之泛型

    Kotlin学习之泛型 Kotlin的泛型与Java一样,都是一种语法糖,只在源代码里出现,编译时会进行简单的字符...

  • Kotlin学习笔记 - 泛型

    1. 基本用法 2. 型变 型变包括 协变、逆变、不变 三种: 协变:泛型类型与实参的继承关系相同 逆变:泛型类型...

  • Kotlin学习笔记:泛型

    Kotlin学习笔记:概述Kotlin学习笔记:基本语法和函数Kotlin学习笔记:类和接口Kotlin学习笔记:...

  • 泛型

    与Java泛型相同,Kotlin同样提供了泛型支持。对于简单的泛型类、泛型函数的定义,Kotlin 与 Java ...

  • Kotlin---泛型

    Kotlin不变型泛型 Kotlin的不变型泛型和Java一样,通过声明泛型类型来使用泛型类。而该种泛型声明后,则...

  • Kotlin学习笔记(七)-泛型

    [toc] 前言 这节我们说下Kotlin的泛型。首先默认大家对Java泛型有个基本的认识,如果 不熟悉Java的...

  • Kotlin 泛型 VS Java 泛型

    建议先阅读我的上一篇文章 -- Java 泛型 和 Java 泛型一样,Kotlin 泛型也是 Kotlin 语言...

网友评论

    本文标题:Kotlin学习笔记之 10 泛型

    本文链接:https://www.haomeiwen.com/subject/nopzvqtx.html