kotlin入门之泛型

2022-02-09 16:12:20 浏览数 (1)

【码上开学】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泛型具有协变性,协变就是允许上面 的赋值是合法的。

它有两层意思:

  1. 其中?是一个通配符,表示这个List的泛型类型是一个未知类型
  2. extends 限制了这个未知类型的上界,也就是泛型类型必须满足这个extends的 限制条件

这里和定义classextends 关键字有点不一样:

  • 它的范围不仅仅是所有直接或者间接子类,还包括上界定义的父类本身,也就是 TextView
  • 它还有implements的意思,即这里的上界也可以是interface.

这里的ButtonTextView的子类,所以满足泛型类型的限制条件,因而能够成功 赋值。

以下几种情况也可以赋值成功.

代码语言:javascript复制
List<? extends TextView> textViews=new ArrayList<TextView>(); //本身
List<? extends TextView> textViews=new ArrayList<Button>(); //直接子类
List<? extends TextView> textViews=new ArrayList<RadioButton>(); //间接子类

一般的集合类包含了getadd的两种操作,比如Java中的List

代码语言:javascript复制
public interface List<E> extends Collection<E>{
    E get(int index);
    boolean add(E e);
}

上面代码中的 E表示泛型类型的符号。

我们看看在使用上界通配符后,List的使用上有没有什么问题:

代码语言:javascript复制
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

根据上面的例子,TextViewButton的父类行,也就能满足super的限制条件, 就可以成功赋值了。

代码语言:javascript复制
List<? super Button> buttons=new ArrayList<Button>(); //本身
List<? super Button> buttons=new ArrayList<TextView>(); //直接父类
List<? super Button> buttons=new ArrayList<Object>(); //间接父类

对于使用了下界通配符的List,我们在看看getadd操作:

代码语言:javascript复制
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 中的outin

kotlinjava泛型一样,kotlin中的泛型本身也是不可变的。 -使用关键字out来支持协变,等同于Java中的上界通配符? extends -使用关键字in来支持逆变,等同于Java中的上界通配符? super

代码语言:javascript复制
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

代码语言:javascript复制
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()
    }

内联特化

在讲解内联特化时我们需要知道泛型的原理。

  • 伪泛型:编译时擦除类型,运行时无实际类型生成
    • 例如:javakotlin
  • 真泛型:编译时生成真实类型,运行时也存在该类
    • 例如: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>(""" {...}""") //泛型参数

0 人点赞