Kotlin 看似递归的编译模板类型

Tho*_*ook 3 generics templates kotlin

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

interface Bar<T>
interface Foo<T: Bar<T>> // Surely this is recursive?
Run Code Online (Sandbox Code Playgroud)

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

interface Foo<T>-一个FooT,没有任何限制

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

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

interface Bar<T>- a Barof T, 没有约束T

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

哦哦,如何T定义Bar<T>?。

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

我们还没有解决TBar......为了便于讨论,让我们来代替T一次。现在我们已经T成为一个Bar<Bar<Bar<T>>>. 这肯定是无穷大吗?

Moi*_*ira 5

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

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

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

fun <T> findMax(collection: Collection<T>): T?
Run Code Online (Sandbox Code Playgroud)

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

fun <T> findMax(collection: Collection<Comparable<T>>): Comparable<T>?
Run Code Online (Sandbox Code Playgroud)

就这样。对?

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

现在假设我们尝试:

fun <T : Comparable<T>> findMax(collection: Collection<T>): T?
Run Code Online (Sandbox Code Playgroud)

好多了。这确保:

  • TComparable
  • 更重要的T是,可以媲美自己

这同样适用于类和继承。

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

class A : SelfReturner<A> {
    override fun getSelf(): A // <--
}
Run Code Online (Sandbox Code Playgroud)

由于返回类型协方差,这可以正常工作,因为ASelfReturner<A>.

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

class Impostor : SelfReturner<A> {
    override fun getSelf(): A // <-- oops!
}
Run Code Online (Sandbox Code Playgroud)

虽然你对这些泛型的明显递归性是正确的,因为人们确实可以

fun <T : Comparable<Comparable<Comparable<...>>>> findMax(collection: Collection<T>): T?
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


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

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

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

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