泛型约束的必要性
泛型约束是对类或者方法中的类型变量进行约束。当创建一个泛型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>
进行详细讲解。
- 调用泛型上界类中的方法
如果泛型约束中指定了类型参数的上界,则可以调用定义在上界类中的方法。接下来我们通过一个案例来调用上界类中的方法,具体代码如下所示。
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()方法。
- 泛型约束<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值。
酷客教程相关文章:
评论前必须登录!
注册