翻译说明:
原标题: When (and when not) to Use Type Parameter Constraints in Kotlin
原文地址: https://typealias.com/guides/when-to-use-type-parameter-constraints/
原文作者: Dave Leeds
翻译系列:
原创系列:
实战系列:
今天这篇文章依旧很简单,只要搞懂一个东西就可以了。那就是泛型中的类型形参的约束,这个概念在Java中也有的。但是我们有个疑惑是什么情况下使用泛型类型形参呢?
当你在声明一个泛型时,Kotlin允许你给这个泛型的类型形参增加约束条件,换言之就是把类型形参可接受的类型实参限制在一个类型范围内。那这个有什么作用呢?让我们一起来看下面这个例子:
想像下有这么个需求场景: 假设你家里有几只宠物,你想选择一个最喜欢的:
fun <T> chooseFavorite(pets: List<T>): T {
val favorite = pets[random.nextInt(pets.size)]
// This next line won't compile - because `name` can't be resolved
println("My favorite pet is ${favorite.name}")
return favorite
}
println()
函数声明不会通过编译,因为你无法通过T
来引用name
属性。因为T
可以是调用者指定的任何内容。例如,像以下代码实现,T
就是一个Int类型,很明显它就没有name属性:
chooseFavorite(listOf(1, 2, 3))
你可以尝试改造一下这个函数,把泛型去掉:
fun chooseFavorite(pets: List<Pet>): Pet {
val favorite = pets[random.nextInt(pets.size)]
println("My favorite pet is ${favorite.name}")
return favorite
}
这个处理看起来貌似没有什么问题,但是我们会遇到一种不想要的返回值类型。以下是我们调用该函数时会发生的情况:
val pets: List<Dog> = listOf(Dog("Rover"), Dog("Sheba"))
val favorite: Pet = chooseFavorite(pets)
尽管我们声明定义的是List< Dog >,但是通过chooseFavorite返回的是一个Pet类型,除非这里我们使用强制类型转换。
我们可以通过指定上限来限制类型形参-换句话说就是指定你想要接收的超类型。在我们的例子中,我们希望此函数作为List
处理List
,List
也就是既包含Cat
类型也包含Dog
类型的混合Pet
类型的列表中。
fun <T : Pet> chooseFavorite(pets: List<T>): T {
val favorite = pets[random.nextInt(pets.size)]
println("My favorite pet is ${favorite.name}")
return favorite
}
这里的类型形参的声明是
,这个Pet
就是上界约束。现在我们已经指定了这个,调用代码只能传递Pet类型以及它的子类型。
val pets: List<Dog> = listOf(Dog("Rover"), Dog("Sheba"))
val favorite: Dog = chooseFavorite(pets)
下面两种是你需要使用类型形参约束情况:
这是一个快速的“备忘单”表,可帮助您决定哪种情况使用什么?
需要调用成员(类的成员函数或属性) | 不需要调用成员(类的成员函数或属性) | |
---|---|---|
需要保留类型 | 使用带有类型参数约束的泛型 | 使用不带类型参数约束的泛型 |
不需要保留类型 | 使用非泛型和适当的抽象 | 使用Java中的原生态类型 |
约束还有很多 - 您可以指定多个参数的约束,以及对同一参数的多个约束。对于所有细节,请查看概念文章Type Parameter Constraint。您还可以在Kotlin的官方参考文档中快速查看常用约束。
本篇文章核心点在于什么情况下该使用泛型约束,作者总结的很好就是那种表格,理解和掌握了那张表格,那么你在使用泛型形参约束上就会胸有成竹。关于那个表格可能有点难理解,我这里再补充解释一下:
List
,但是chooseFavorite方法返回依然是父类Pet,外部接收类型Dog所以避免不了强制类型转换。总之一句话: 是否需要保留类型,也就直接决定了是否使用泛型,如果不保留类型的话可以不使用泛型,函数外部接收者可以使用抽象的父类来接收;如果保留类型,函数外部接收者就必须是明确一个类型,那么此时如果还用抽象父类就避免不了类型转换,那么此时就应该使用泛型1、是否需要调用成员决定了在使用泛型前提下,是否使用泛型类型参数约束;否则直接可以使用抽象
2、是否保留类型决定了是否使用泛型
3、既保留类型又调用成员,则就是使用泛型且带形参约束条件
4、不保留类型又调用成员,则就是不使用泛型和适当的抽象
5、保留类型不调用成员,则就是使用泛型不带形参约束条件
6、不保留类型不调用成员,则就是使用java中原生态类型,例如Java中的List类型
欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~
Kotlin邂逅设计模式系列:
数据结构与算法系列:
翻译系列:
原创系列:
Effective Kotlin翻译系列
实战系列: