Kotlin 泛型约束

泛型约束的必要性

泛型约束是对类或者方法中的类型变量进行约束。当创建一个泛型List<E>时,类型变量E理论上是可以被替换为任意的引用类型,但是有时候需要约束泛型实参的类型,例如想对E类型变量求和,则E应该是Int类型、Long类型、Double类型或者Float类型等,而不应该是String类型,因此在特殊情况下,需要对类型变量E进行限制。接下来我们通过一个案例来学习泛型约束的相关知识,具体代码如下所示。

package com.coolcou.chapter07
fun <T : Number> List<T>.sum(): Double? {
    var sum: Double? = 0.0                    //定义一个Double 类型的变量sum
    for (i in this.indices) {                 //遍历传递过来的集合中的数据
        sum = sum?.plus(this[i].toDouble())   //转化为Double 类型再与sum 相加
    }
    return sum
}
fun main(args: Array<String>) {
   var list = arrayListOf(1, 2, 3, 4, 5)     //创建一个集合变量list
   println(" 求和:${list.sum()}")
}

运行结果:

求和:15.0

上述代码中,创建了一个泛型方法sum(),该方法的返回值为Double类型,泛型为List<T>,其中对类型变量T的约束为Number类型,也就是调用sum()方法的集合中的类型必须是Number类型。在main()方法中可以看到,创建了一个list集合,这个集合中的数据设置的都是Int类型,Int类型属于Number类型,因此在该程序中可以通过list集合来调用sum()方法实现求和功能。如果传入的类型不属于Number类型则会抛出类型不匹配异常。

泛型约束<T:类或接口>

泛型约束<T:类或接口>与Java中的<? extends类或接口>类似,这个约束也可以理解为泛型的上界。例如泛型约束<T:BoundingType>,其中BoundingType可以称为绑定类型,绑定类型可以是类或者接口。如果绑定类型是一个类,则类型参数T必须是BoundingType的子类。如果绑定类型是一个接口,则类型参数T必须是BoundingType接口的实现类。接下来我们来针对如何调用泛型上界类中的方法与泛型约束<T:Any?><T:Any>进行详细讲解。

  1. 调用泛型上界类中的方法
    如果泛型约束中指定了类型参数的上界,则可以调用定义在上界类中的方法。接下来我们通过一个案例来调用上界类中的方法,具体代码如下所示。
package com.coolcou.chapter07
fun <T : Number> twice(value: T): Double {
    return value.toDouble()  *  2
}
fun main(args: Array<String>) {
    println("4.0 的两倍:${twice(4.0f)}")// 将4.0f 传递到twice()中并打印结果
    println("4 的两倍:${twice(4)}")  //将4 传递到twice()中并打印结果
}

运行结果:

4.0的两倍:8.0
4的两倍:8.0

在上述代码的twice()方法中,参数value调用的toDouble()方法是在Number类中定义的。由于在泛型约束<T:Number>中已经指定类型参数的上界为Number,因此twice()方法中传递的参数value可以调用定义在上界类Number中的方法。
如果上界约束需要多个约束,则可以通过where语句来完成。接下来我们通过where关键字来实现上界约束的多个约束,具体示例代码如下:

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

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

  1. 泛型约束<T:Any?>与<T:Any>
    在泛型<T:类或者接口>中,有两种特别的形式,分别是<T:Any?><T:Any>,其中<T:Any?>表示类型实参是Any的子类,且类型实参可以为null。<T:Any>表示类型实参是Any的子类,且类型实参不可以为null。在Kotlin中,Any类型是任意类型的父类型,类似Java中的Object类,因此声明的<T:Any?>等同于<T>。接下来我们通过一个案例来演示如何使用泛型<T:Any?>,具体代码如下所示。
package com.coolcou.chapter07
// 声明<T : Any?>等同于<T>
fun <T : Any?> nullAbleProcessor(value: T) {
    value?.hashCode()
}
fun <T : Any> nullDisableProcessor(value: T) {
    value.hashCode()  //编译通过
}
fun main(args: Array<String>) {
   nullAbleProcessor(null)
   // nullDisableProcessor(null)   编译错误
}

上述代码中,nullAbleProcessor()方法中的类型参数T使用的是<T:Any?>进行约束的,<T:Any?>表示可以接收任意类型的类型参数,这个任意类型中包含null,因此在main()方法中调用nullAbleProcessor()方法时,这个方法中可以传递null。nullDisableProcessor()方法中传递的类型参数T使用的是<T:Any>进行约束的,<T:Any>表示可以接收任意类型的类型参数,这个任意类型中不包含null,因此在main()方法中调用nullDisableProcessor()方法,且这个方法中传递null时,编译器会自动提示“is not satisfied:inferred type Nothing? is not a subtype of Any”,也就是“类型匹配不成功,传递的null不是Any的子类型”

如果想在上述代码的nullDisableProcessor()方法中传递null,则可以将传递的参数类型T改为T?,编译就可以通过了。修改后的代码如下所示。

package com.coolcou.chapter07
// 声明<T : Any?>等同于<T>
fun <T : Any?> nullAbleProcessor(value: T) {
    value?.hashCode()
}
fun <T : Any> nullDisableProcessor(value: T?) {
    value?.hashCode()  // 编译通过
}
fun main(args: Array<String>) {
   nullAbleProcessor(null)
   nullDisableProcessor(null)
}

上述代码中,将nullDisableProcessor()方法中传递的参数类型T改为T?,第7行代码从上面代码中的“value.hashCode()”改为了“value?.hashCode()”,这样修改后即使使用<T:Any>进行约束传递的参数类型,也可以允许该方法中传递null值。

酷客教程相关文章:

赞(0)

评论 抢沙发

评论前必须登录!