首页 > 解决方案 > Kotlin 看似递归的可编译模板类型

问题描述

在工作中进行代码审查并遇到了我以前从未见过的模板类型的使用。乍一看,代码似乎不应该编译,因为定义似乎是递归的。我将其归结为最简单的可验证示例:

interface Bar<T>
interface Foo<T: Bar<T>> // Surely this is recursive?

我对模板类型如何工作的理解是:

interface Foo<T>- 一个FooT无约束

interface Foo<T : Bar>- a Fooof T, whereT被限制为 aBar

假设我上面说的是真的,那么这对我来说没有意义:

interface Bar<T>- a Barof T, 无约束T

interface Foo<T: Bar<T>>- a Fooof T, whereT被限制为 aBar<T>

呃哦,怎么能用?T来定义Bar<T>

我们知道T是 a Bar<T>,所以如果我们替换Tin Bar<T>,那么它就是 a Bar<Bar<T>>

我们还没有解决......T为了Bar争论,让我们T再次替换。现在我们已经T成为一个Bar<Bar<Bar<T>>>. 这肯定会进入无穷大,不是吗?

标签: templatesgenericskotlin

解决方案


CRTP(递归有界量化)是一种众所周知的设计习惯用法,通常(除其他外)用于为通用代码提供某种“自我”类型

这是递归泛型的一个实际示例。

假设您有一个对一组可比较值进行操作的函数。

fun <T> findMax(collection: Collection<T>): T?

理想情况下,我们希望将此函数限制为仅对Comparable值集合进行操作:

fun <T> findMax(collection: Collection<Comparable<T>>): Comparable<T>?

就这样。正确的?

虽然这可行,但您需要对返回值进行强制转换才能执行任何有用的操作,因为它返回的是 aComparable<T>而不是 a T

现在假设我们尝试:

fun <T : Comparable<T>> findMax(collection: Collection<T>): T?

好多了。这可确保:

  • TComparable
  • 更重要的T是,与自己相媲美

这同样适用于类和继承。

interface SelfReturner<T : SelfReturner<T>> {
    fun getSelf(): T
}

class A : SelfReturner<A> {
    override fun getSelf(): A // <--
}

由于返回类型协方差,这工作得很好,因为ASelfReturner<A>.

这通常用于允许一个类“知道”它自己的类型,但重要的是要记住它不是万无一失的:

class Impostor : SelfReturner<A> {
    override fun getSelf(): A // <-- oops!
}

虽然您对这些泛型的明显递归性是正确的,因为确实可以改为编写

fun <T : Comparable<Comparable<Comparable<...>>>> findMax(collection: Collection<T>): T?

这不会永远持续下去,因为在单级递归之后通常会满足条件(例如String,我们使用 . 它a Comparable<String>,这就是编译器需要检查的全部内容。)

请注意,与例如 C++ 不同,Kotlin 不使用模板。泛型类型信息仅由编译器用于确保代码正确性,并且不会在编译后的代码中保留*(请参阅类型擦除)。

而模板实例化将导致创建一个新的和完全独立的类型,泛型类型在运行时都被删除到同一个(非泛型)类。

* 这并不完全正确;一些通用类型信息可用于反射,这就是类型标记起作用的原因,但它仅在有限的情况下可用。


有趣的事实:维基百科声称这是偶然发现的,

作者 Jan Falkin,他不小心从派生类派生了一个基类

因此,即使对于提出这个概念的人来说,它似乎也同样令人困惑。

是的,没有引用,但我们不要破坏魔法。:)


推荐阅读