前言

简单记录一下泛型的使用,记录于此,方便自己查阅。

正文

什么是泛型

泛型即“参数化类型”,就是将具体的类型变成参数化类型,在声明一个泛型时,传递的是一个类型形参,在调用时传递的是一个类型实参。

当定义泛型时,泛型是在类型名之后、主构造函数之前用尖括号“<>”括起来的大写字母类型参数。当定义泛型类型变量时,可以完整地写明类型参数,如果编译器可以自动推断类型参数,则可以省略类型参数。

class ArrayList<E>

上述代码中E表示某种类型,定义时是一个未知类型,称为类型形参。E这个类型类似一个参数,当创建ArrayList实例时,需要传入具体的类型。

示例代码如下:

var list1 = arrayListOf<String>("aa","bb","cc") 
var list2 = arrayListOf<Long>(111,222,333) 
var list3 = arrayListOf<Int>(1,2)

上面传入的参数String、Long、Int都是类型实参。

泛型分类

泛型分为3种类型,分别是泛型类、泛型接口以及泛型方法

泛型类

使用泛型标记的类,被称为泛型类。泛型类的使用分为两种情况,一种是泛型类被用于实例化,另一种是泛型类被用于继承。

当泛型类用于实例化时,需要传递具体的类型实参。

泛型的符号一般使用大写字母,如<T>、<E>、<K>、<V>等,泛型符号可以是满足Kotlin命名规则的任意字符,甚至可以是某一个单词,如<TYPE>,一个类可以声明多个泛型,只要使用不同的泛型符号即可

  1. 泛型类被用于实例化

val list = ArrayList<String>()     //String类型为泛型实参 
val map = HashMap<String, Int>()  //String、Int 为泛型实参 
val set = HashSet<Long>()          //Long 为泛型实参  
  1. 泛型类被用于继承

当泛型类被用于继承时,需要为泛型形参提供一个具体类型或者另一个类型的形参。

open abstract class Parent<T> {
    abstract fun get(): T;
}
//1. 继续泛型(这样可以传入Int,Long,Double等)
class Son<T> : Parent<T>() {
    override fun get(): T {
        TODO("Not yet implemented")
    }
}
//2. 具体的一个类型
class Son2<Int> : Parent<Int>() {
    override fun get(): Int {
        TODO("Not yet implemented")
    }
}
泛型接口

使用泛型标记的接口,被称为泛型接口。泛型接口的使用分为两种情况。

  1. 情况一

泛型接口被实现时已经确定泛型接口对应的实参,直接传递实参。

interface List<String> : Collection<String>{} 
  1. 情况二

泛型接口被实现时不能够确定泛型接口对应的实参,则需要使用当前类或者接口的泛型形参

interface List<E> : Collection<E>{}
泛型方法

使用泛型标记的方法,被称为泛型方法。

泛型方法在被调用时,只需传入具体的泛型实参即可。

//String类型为泛型实参,Kotlin自动推断 
val list = arrayListOf("a","b","c")  
//String、Int 为泛型实参,Kotlin自动推断 
val map = hashMapOf("a" to 1,"b" to 2)  
//Long 为泛型实参,Kotlin自动推断 
val set = hashSetOf(1L,2L,3L)  

当然,也可以自己定义泛型方法。

fun <T> printType(a: T) {
    when (a) {
        is Int -> {
            "Int 类型"
        }
        is String -> {
            "String 类型"
        }
        is Double -> {
            "Double 类型"
        }
    }
}

泛型约束

泛型约束是对类或者方法中的类型变量进行约束。

当创建一个泛型List<E>时,类型变量E理论上是可以被替换为任意的引用类型,但是有时候需要约束泛型实参的类型,例如想对E类型变量求和,则E应该是Int类型、Long类型、Double类型或者Float类型等,而不应该是String类型,因此在特殊情况下,需要对类型变量E进行限制。

泛型约束格式:

<T:Type>

其中Type可以称为绑定类型,绑定类型可以是类或者接口。

  1. 如果绑定类型是一个类,则类型参数T必须是Type的子类。

  2. 如果绑定类型是一个接口,则类型参数T必须是Type接口的实现类。

调用泛型上界类中的方法

上界是Number类型的。Double,Int等都是继承Number。如果传入其他类型,会提示不匹配。

  1. 一个约束条件

fun <T : Number> add(a: T, b: T): Double {
    return a.toDouble() + b.toDouble();
}
add(100, 100)
add(10f, 100f)
//类型不匹配[错误]
add("a", "b")
  1. 多个约束条件

如果上界约束需要多个约束,则可以通过where语句来完成。

fun <T> manyConstraints(value: T) where T : CharSequence, T : Appendable { 
    if (!value.endsWith('.')) { 
        value.append('.') 
    } 
}

通过where关键字实现了上界约束的多个约束,每个约束中间用逗号分隔,并且传递的参数value可以调用第1个约束CharSequence类中的endsWith()方法,同时也可以调用第2个约束Appendable类中的append()方法。

泛型约束<T:Any?>与<T:Any>

在泛型<T:类或者接口>中,有两种特别的形式,分别是<T:Any?>和<T:Any>。

  1. <T:Any?>表示类型实参是Any的子类,且类型实参可以为null。

  2. <T:Any>表示类型实参是Any的子类,且类型实参不可以为null。

// 声明<T : Any?>等同于<T>
fun <T : Any?> nullAbleProcessor(value: T) {
    value?.hashCode()
}
fun <T : Any> nullDisableProcessor(value: T) {
    value.hashCode()
}

使用情况

//编译成功
nullAbleProcessor(null)

<T:Any?>表示可以接收任意类型的类型参数,这个任意类型中包含null。

//无法编译,提示[Null can not be a value of a non-null type TypeVariable(T)]
nullDisableProcessor(null)

<T:Any>表示可以接收任意类型的类型参数,这个任意类型中不包含null。

如果要可传输null,需要改为

fun <T : Any> nullDisableProcessor(value: T?) {
    value?.hashCode()
}

参考文章

  1. 《Kotlin从基础到实战》

相关文章

暂无评论

none
暂无评论...