如何在Kotlin中编写强类型的泛型扩展函数?

时间:2019-07-26 16:45:17

标签: generics kotlin extension-methods type-inference

关注 strong 通用部分。

假设我具有此扩展功能:

fun <E> Collection<E>.myContains(item: E) : Boolean {
    // quite pointless, I know, but a simple example
    return item in this
}

目的是编写一个仅接受集合元素类型(E)的函数,但是编译器未对其进行验证吗?!

val isItInside: Boolean = listOf(1, 2).myContains("1")

愉快地编译。我的猜测是E被推断为Any

如何在Kotlin类型系统/泛型中强制执行此限制?

(Kotlin版本1.3.41)


原始上下文

尝试编写一个小的断言框架的练习。稍微复杂一点,但是尝试获得上面最简单的repro。

class Asserter<T>(val value: T)

infix fun <T> T.should(block: Asserter<T>.() -> Unit) =
    Asserter(this).block()

fun <T : Collection<*>> Asserter<T>.haveSize(size: Int) {
    check(this.value.size == size) {
        "expecting ${this.value} to be of size $size"
    }
}

fun <E, T : Collection<E>> Asserter<T>.contain(item: E) {
    check(item in this.value) {
        "$item does not exist in $item"
    }
}

class ShouldTest {

    @Test fun intList() {
        listOf(1, 2) should {
            haveSize(2)
            contain(1)
            contain("2") // this shouldn't compile
        }
    }

    @Test fun stringList() {
        listOf("1", "2") should {
            haveSize(2)
            contain(1) // this shouldn't compile
            contain("2")
        }
    }
}

2 个答案:

答案 0 :(得分:4)

这似乎是由于Collection<out E>接口参数(定义为Collection<Any>)上的差异所致。

这意味着Collection<E>Collection<Any>.myContains()的超类型,因此(显然)Collection<Int>扩展名可以在MutableCollection上调用。

您可以通过将其替换为不变的listOf()(以及将mutableListOf()替换为E)来确认这一点;您会按预期收到编译时“类型不匹配”错误。

这让我感到惊讶。我猜编译器必须同时使用接收器类型和参数类型来推断<!doctype html> <html> <head> <meta charset='utf8'> <meta name='viewport' content='width=device-width'> <title>Svelte app</title> <link rel='icon' type='image/png' href='favicon.png'> <link rel='stylesheet' href='global.css'> <link rel='stylesheet' href='bundle.css'> </head> <body> <script src='bundle.js'></script> </body> </html> 。 (任何人都可以确认吗?)正如您所指出的那样,它具有阻止更严格的类型安全性的烦人作用。

答案 1 :(得分:0)

这看起来像一个解决方案:

class Asserter<in T>(val value: @UnsafeVariance T)