递归类型绑定
您所指的模式在 JVM 世界中称为 递归类型绑定。在泛型中,当引用类型具有由引用类型本身绑定的类型参数时,则称该类型参数具有递归类型绑定。
比如在泛型类型Fruit<T extends Fruit<T>>中,Fruit是引用类型,它的类型参数T是由Fruit本身绑定的,所以,类型参数T有一个递归类型绑定Fruit<T>.
让我们解决一个简单的问题来逐步理解这个概念。
问题
假设我们必须按照水果的大小对水果进行分类。而且我们被告知我们只能比较相同类型的水果。例如,我们无法将苹果与橙子进行比较(双关语)。
所以,我们创建一个简单的类型层次结构,如下所示,
Fruit.kt
interface Fruit {
val size: Int
}
Apple.kt
class Apple(override val size: Int) : Fruit, Comparable<Apple> {
override operator fun compareTo(other: Apple): Int {
return size.compareTo(other.size)
}
}
Orange.kt
class Orange(override val size: Int) : Fruit, Comparable<Orange> {
override operator fun compareTo(other: Orange): Int {
return size.compareTo(other.size)
}
}
测试
fun main() {
val apple1 = Apple(1)
val apple2 = Apple(2)
println(apple1 > apple2) // No error
val orange1 = Orange(1)
val orange2 = Orange(2)
println(orange1 < orange2) // No error
println(apple1 < orange1) // Error: different types
}
解决方案
在这段代码中,我们能够实现能够比较相同类型的目标,即苹果与苹果,橙子与橙子。当我们比较苹果和橙子时,我们会得到一个错误,这正是我们想要的。
问题
这里的问题是实现compareTo() 方法的代码对于Apple 和Orange 类是重复的。并且将在我们从Fruit 扩展的所有类中重复更多,当我们将来创建新的水果时。我们的示例中的重复代码量较少,但在现实世界中,每个类中的重复代码可能有数百行。
将重复代码移至接口
Fruit.kt
interface Fruit : Comparable<Fruit> {
val size: Int
override operator fun compareTo(other: Fruit): Int {
return size.compareTo(other.size)
}
}
Apple.kt
class Apple(override val size: Int) : Fruit
Orange.kt
class Orange(override val size: Int) : Fruit
解决方案
这一步,我们去掉compareTo()方法的重复代码,把它移到界面上。我们的扩展类Apple 和Orange 不再被通用代码污染。
问题
现在的问题是我们现在能够比较不同的类型,比较苹果和橙子不再给我们一个错误:
println(apple1 < orange1) // No error
引入类型参数
Fruit.kt
interface Fruit<T> : Comparable<T> {
val size: Int
override operator fun compareTo(other: T): Int {
return size.compareTo(other.size) // Error: size not available.
}
}
Apple.kt
class Apple(override val size: Int) : Fruit<Apple>
Orange.kt
class Orange(override val size: Int) : Fruit<Orange>
解决方案
为了限制不同类型的比较,我们引入了一个类型参数T。所以可比较的Fruit<Apple> 不能与可比较的Fruit<Orange> 比较。注意我们的Apple 和Orange 类;它们现在分别继承自 Fruit<Apple> 和 Fruit<Orange> 类型。现在,如果我们尝试比较不同的类型,IDE 会显示错误,这是我们想要的行为:
println(apple1 < orange1) // Error: different types
问题
但是在这一步中,我们的Fruit 类没有编译。编译器不知道T 的size 属性。这是因为我们的类型参数T
Fruit 类没有任何限制。所以,T 可以是任何类,世界上的每个类都不可能有一个size 属性。所以编译器没有识别T的size属性是正确的。
引入递归类型绑定
Fruit.kt
interface Fruit<T : Fruit<T>> : Comparable<T> {
val size: Int
override operator fun compareTo(other: T): Int {
return size.compareTo(other.size)
}
}
Apple.kt
class Apple(override val size: Int) : Fruit<Apple>
Orange.kt
class Orange(override val size: Int) : Fruit<Orange>
最终解决方案
所以,我们告诉编译器我们的T 是Fruit 的子类型。换句话说,我们指定上限T extends Fruit<T>。这确保只允许 Fruit 的子类型作为类型参数。现在编译器知道size 属性可以在Fruit 类(Apple、Orange 等)的子类型中找到,因为Comparable<T> 也接收我们的包含@ 的类型(Fruit<T>) 987654375@财产。
这让我们摆脱了compareTo()方法的重复代码,也让我们可以比较相同类型的水果,苹果和苹果,橙子和橙子。
更多关于递归类型边界
递归类型是包含一个函数的类型,该函数将该类型本身用作某个参数或其返回值的类型。在我们的示例中,compareTo(other: T) 是递归类型的函数,它采用相同的递归类型作为参数。
警告
此模式中的警告是编译器不会阻止我们创建具有其他子类型的类型参数的类:
class Orange(override val size: Int) : Fruit<Orange>
class Apple(override val size: Int) : Fruit<Orange> // No error
请注意,在上面的 Apple 类中,我们错误地传递了 Orange 而不是 Apple 本身作为类型参数。这导致compareTo(other: T) 方法采用Orange 而不是Apple。
现在我们在比较不同类型时不再出错,并且突然无法将苹果与苹果进行比较:
println(apple1 < orange1) // No error
println(apple1 > apple2) // Error
因此,开发人员在扩展类时需要小心。
不可能无限递归
声明Fruit<T extends Fruit<T>> 确保编译器只允许Fruit<T> 类型的子类型。 Fruit<Fruit<T>> 或 Fruit<Fruit<Fruit<T>>> 等不是Fruit<T> 的子类型,也就是说,它们不在范围内。
例如,如果我们以如下方式使用声明:
class Orange(override val size: Int) : Fruit<Fruit<Orange>>
编译器会报错:Type argument is not within its bound
Fruit<Fruit> 没有可以想象的用例,因此编译器也不允许这样做。只允许第一级,即Fruit<Apple>、Fruit<Orange>等。
这两件事一起防止了无限递归。
就是这样!希望对您有所帮助。