ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ## 泛型约束 ### 泛型约束的必要性 **泛型约束是对类或者方法中的类型变量进行约束**。当创建一个泛型List<E>时,类型变量E理论上是可以被替换为任意的引用类型,但是**有时候需要约束泛型实参的类型**,例如想对E类型变量求和,则E应该是Int类型、Long类型、Double类型或者Float类型等,而不应该是String类型,因此在特殊情况下,需要对类型变量E进行限制。接下来我们通过一个案例来学习泛型约束的相关知识,具体代码如下所示。 ``` 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>`进行详细讲解。 #### 调用泛型上界类中的方法 **如果泛型约束中指定了类型参数的上界,则可以调用定义在上界类中的方法**。 接下来我们通过一个案例来调用上界类中的方法,具体代码如下所示。 ``` 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?>,具体代码如下所示。 ``` //声明<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?,编译就可以通过了。修改后的代码如下所示。 ``` //声明<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?,第6行代码从上面的`“value.hashCode()”`改为了`“value?.hashCode()”`,这样修改后即使使用`<T:Any>`进行约束传递的参数类型,也可以允许该方法中传递null值。