Kotlin(四)泛型

作者: zcwfeng | 来源:发表于2020-12-25 17:30 被阅读0次

4.1 泛型:类型安全

对比java的历史,java1.5 开始引入的 泛型。同样,Kotlin泛型也是很重要的

泛型的优势:

  • 类型检查,能再编译的时候帮你检查错误
  • 自动类型转换,获取数据的时候不需要进行类型强制转换
    (更加语义话,能够写出更加通用的代码)

4.2 使用

假设有一个小的需求使用:

定义一个find方法,传入一个对象,若列表中存在该对象,则返回对象,不存在则返回空

由于原有的集合类不存在这样一个方法,所以可以定义一个新的集合类,同样要声明泛型。

class SmartList<T>: ArrayList<T>() {
    fun find(t:T):T?{
        val index = super.indexOf(t)
        return if(index >= 0) super.get(index) else return null
    }
}

fun main() {
    val smartList = SmartList<String>()
    smartList.add("one")
    println(smartList.find("one"))
    println(smartList.find("two").isNullOrEmpty())
}

除了定义泛型,还可以利用泛型扩展函数来实现

fun<T> ArrayList<T>.find(t:T):T?{
    val index = this.indexOf(t)
    return if(index >= 0) this.get(index) else return null
}

fun main() {
//    val smartList = SmartList<String>()
    val smartList = ArrayList<String>()
    smartList.add("one")
    println(smartList.find("one"))
    println(smartList.find("two").isNullOrEmpty())
}

kotlin中,var arraylist = ArrayList(); 是错误不被允许的。Java中可以这么做原因是Java1.5引入泛型,为了向前兼容List list = new ArrayList() 这种存在线上很久的代码。而Kotlin是基于Java1.6 所以没有兼容问题,这么做就不允许。
Kotlin中还有类型推导,所以 var arraylist = arrayListOf("one","two") 这样的方式是可以的。

4.3 类型约束:设定类型上下界

定义一个盘子,可以存放任何东西
class Plate<T>(val t:T)

我们想给Plate归类,一类放水果一类放蔬菜

定义水果
open class Fruit(val weight:Double)
class Apple(weight: Double):Fruit(weight)
class Banana(weight: Double):Fruit(weight)

定义水果盘子类型
class FruitPlate<T:Fruit>(val t:T)

两种实例写法

val applePlate = FruitPlate<Apple>(Apple(100.0))
val applePlate2= FruitPlate(Apple(100.0))

和java类似,java语法用extends,kotlin 中用“:”,这种类型的泛型约束我们称之为 上界约束

我们会出现这种情况

val applePlate3 =FruitPlate(null)

上面声明的应该是一个参数不可空的类型,实例报错,而我们的果盘是可以空着的 ,那么我们应该改成

class FruitPlate<T:Fruit?>(val t:T)

上面是类型约束都是单个条件,比如类型上界是什么,是否可空。那么多个条件如何处理-----where 关键字

有一把刀可以用来切水果,我们可以实现如下:

interface Ground{}

class Watermelon(weight: Double):Fruit(weight),Ground

fun <T> cut(t:T) where T:Fruit,T:Ground{
    println("you can cut me")
}

cut(Watermelon(100.0)) // 允许
//    cut(Apple(2.0))//不允许

4.4泛型的背后:类型擦除

Java 无法声明一个泛型数组

思考:Apple[] 和 Fruit[]以及 List<Apple> 和 List<Fruit>

Apple[] appleArray =  new Apple[10];
Fruit[] fruitArray = appleArray;// 允许
fruitArray[0] = new Banana(0.5);// 编译通过,运行报ArrayStoreException
List<Apple> appleList=new ArrayList<Apple>();
List<Fruit> fruitList = appleList;// 不允许

数组是协变的,而List是不变的

Object[] 是所有对象数组的父类,List<Object> 和 List<T> 没有这种父子关系

java的泛型是类型擦除的,是一个伪泛型。代码对比一下

System.out.println(appleArray.getClass());
System.out.println(appleList.getClass());

运行结果

class [Ljavat.Apple;
class java.util.ArrayList

说明数组不支持泛型

Kotlin 中的泛型机制和java一样,所以上面特性在kotlin中同样存在。

但不同的是,Kotlin 中数组支持泛型,不再协变
不能将任意一个对象数组赋值给Array<Any> 或者 Array<Any?>

val appleArray = arrayOfNulls<Apple>(3)
//    var anyArray:Array<Any?> = appleArray//不允许

我们看段代码

    ArrayList list = new ArrayList();
    ArrayList<String> stringList = new ArrayList<String>();
    String s =stringList.get(0);

ASM反编译后

 public <init>()V
  。。。
   L1
    LINENUMBER 8 L1
    ALOAD 0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    PUTFIELD top/zcwfeng/java/base/数组协变.list : Ljava/util/ArrayList;
   L2
    LINENUMBER 9 L2
    ALOAD 0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    PUTFIELD top/zcwfeng/java/base/数组协变.stringList : Ljava/util/ArrayList;
   L3
    LINENUMBER 11 L3
    ALOAD 0
    ALOAD 0
    GETFIELD top/zcwfeng/java/base/数组协变.stringList : Ljava/util/ArrayList;
    ICONST_0
    INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Object;
    CHECKCAST java/lang/String
    PUTFIELD top/zcwfeng/java/base/数组协变.s : Ljava/lang/String;
    RETURN
。。。

发现L1,L2 声明ArrayList 编译后字节码是一样的。可见类型擦除了。泛型类型自动转换如何呢?L3 字节码中我们看到 他是类型强制转换了。虽然类型擦除了,但是还是通过其他方式保证了泛型的额相关特性。

但是有些时候我们需要获取运行时候的 泛型参数类型,比如序列化和反序列化。

open class Plate1<T>(val t:T,val clazz:Class<T>){
    fun getType(){
        println(clazz)
    }
}


val applePlate1 = Plate1(Apple(1.0),Apple::class.java)
    applePlate1.getType()

使用这种方式获取到了,但是如下不可以

//    val listType = ArrayList<String>::class.java //不允许
//    val mapType = ArrayList<String,String>::class.java//不允许

还有另外的方式可以获取各种类型信息?利用匿名内部类

val list1 =ArrayList<String>()
    val list2= object : ArrayList<String>(){}// 匿名内部类
    println(list1.javaClass.genericSuperclass)
    println(list2.javaClass.genericSuperclass)

输出:
java.util.AbstractList<E>
java.util.ArrayList<java.lang.String>

原因,类型擦除不会将全部的类型信息都擦除,还会 将类型信息放在class常量池中。

设计 一个获取所有类型信息的类
// 获取所有类型信息
open class GenericsToken<T>{
    var type: Type = Any::class.java
    init {
        val superClass = this.javaClass.genericSuperclass
        type = (superClass as ParameterizedType).actualTypeArguments[0]
    }
}


    val gt =object :GenericsToken<Map<String,String>>(){}
    println(gt.type)

输出:
java.util.Map<java.lang.String, ? extends java.lang.String>

通过获取父类或者父类接口泛型类型信息实现我们的需求。我们长用的Gson也是使用了相同的设计。

val json = Gson().fromJson<List<String>>("json 串。。。")
    val rType = object : TypeToken<List<String>>(){}.type //利用泛型擦除遗留的信息,内部类保存了外部类的信息,class的常量池
    val json2 = Gson().fromJson<List<String>>("json 串。。。",rType)
使用函数内联获取泛型
inline fun <reified T>getType(){
    return T::class.java
}

// reified 修饰的inline 方法只有用在kotlin中,java无法调用
inline fun <reified T:Any> Gson.fromJson(json:String):T{
    return Gson().fromJson(json,T::class.java)
}

kotlin普通内联函数可以在Java中调用,他会被当一个普通常规函数,但是reified实例化的参数类型的内联函数则不能被java调用。因为他一直是内联的。

4.5 kotlin中的泛型,协变和逆变

java 中

    List<String> mstringList = new ArrayList<String>();
//    List<Object> objectList = mstringList;// 报错

不能直接赋值,如果可以这样会导致类型安全问题。java泛型明确泛型基本条件是保证安全,所以不支持这种。

kotlin 中

val stringList:List<String> = ArrayList<String>();
    val anyList:List<Any> = stringList

List 源码

public interface List<out E> : Collection<E> {
......
}

泛型前面多了一个out关键字

正常java 中List<String> 不是List<Object> 子类。Kotlin也是这样的规则。但是out 声明后,Kotlin的List支持了协变(类型A是类型B的子类型那么,Generic<A> 也是 Generic<B>的子类型)

// 泛型协变
    val stringList:List<String> = ArrayList<String>();
//    stringList.add("kotlin")// 编译报错 <out E> 只读

支持协变的List只可以读取,不能添加
类似java中 < ? extends Object>

List<? extends Object> mList = new ArrayList<String>();

通常情况一个泛型Generic<T> 支持协变,那么他里面的方法的参数类型不能使用T类型,因为一个方法的参数不能传入参数父类型对象,那样可能导致错误。

泛型不变和协变,还有第三种逆变(类型A是类型B的子类型反过来Generic<B> 是Generic<A>的子类型)

现在对 MutableList<Double>排序

val doubleComparator = Comparator<Double>{
    d1,d2->d1.compareTo(d2)               
}                                         

// 逆变                                                   
val doubleList = mutableListOf(2.0,3.0)                 
doubleList.sortWith(doubleComparator)      

暂时看没什么问题那么如果intComparator,longComparator.用Number 替换子类型


val numberComparator = Comparator<Number> {
    d1,d2 ->  d1.toDouble().compareTo(d2.toDouble())
}
// 逆变
    val doubleList = mutableListOf(2.0, 3.0)
    doubleList.sortWith(numberComparator)
    val intList =  mutableListOf(2,1)
    intList.sortWith(numberComparator)

看下sortWith源码

expect fun <T> MutableList<T>.sortWith(comparator: Comparator<in T>): Unit

in 关键字,逆变(如果A是B的 子类型,那么反过来Genric<B>是Generic<A>的子类型)
和java中的<? super T> 相同的效果。

通配符*

MutableList<*>与MutableList<Any?> 不是统一中列表

MutableList<*> 相当于------> MutableList<out Any?>

相关文章

  • 泛型

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

  • Kotlin---泛型

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

  • Kotlin 泛型 VS Java 泛型

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

  • Kotlin(四)泛型

    4.1 泛型:类型安全 对比java的历史,java1.5 开始引入的 泛型。同样,Kotlin泛型也是很重要的 ...

  • Kotlin for android学习六:泛型

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

  • 泛型

    Kotlin 泛型详解 声明一个泛型类 声明一个泛型方法 泛型约束 List 和 List 是...

  • Kotlin 泛型

    Kotlin 支持泛型, 语法和 Java 类似。例如,泛型类: 泛型函数: 类型变异 Java 的泛型中,最难理...

  • Kotlin:泛型杂谈(下)

    在Kotlin:泛型杂谈(上)中,从泛型扩展属性、非空约束、实例化类型参数三个方面简单介绍了一下Kotlin中泛型...

  • 【Android】 Kotlin(七)泛型

    深入理解Kotlin泛型 Kotlin 的泛型与 Java 一样,都是一种语法糖,即只在源代码中有泛型定义,到了c...

  • Kotlin 泛型

    说起 kotlin 的泛型,就离不开 java 的泛型,首先来看下 java 的泛型,当然比较熟悉 java 泛型...

网友评论

    本文标题:Kotlin(四)泛型

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