Kotlin:泛型和差异

时间:2017-08-08 23:57:35

标签: java generics kotlin covariance

我想在Throwable上创建一个扩展函数,给定KClass,递归搜索与参数匹配的根本原因。以下是一次有效的尝试:

fun <T : Throwable> Throwable.getCauseIfAssignableFrom(e: KClass<T>): Throwable? = when {
    this::class.java.isAssignableFrom(e.java) -> this
    nonNull(this.cause) -> this.cause?.getCauseIfAssignableFrom(e)
    else -> null
}

这也有效:

fun Throwable.getCauseIfAssignableFrom(e: KClass<out Throwable>): Throwable? = when {
    this::class.java.isAssignableFrom(e.java) -> this
    nonNull(this.cause) -> this.cause?.getCauseIfAssignableFrom(e)
    else -> null
}

我像这样调用函数:e.getCauseIfAssignableFrom(NoRemoteRepositoryException::class)

然而,关于泛型的Kotlin docs说:

  

这称为声明站点方差:我们可以注释该类型   Source的参数T,以确保它只返回(生成)   来自Source的成员,从未消费过。为此,我们提供   out修饰符

abstract class Source<out T> {
    abstract fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // This is OK, since T is an out-parameter
    // ...
}

在我的情况下,参数e不会被返回,而是被消耗掉。在我看来,它应该被声明为e: KClass<in Throwable>,但不能编译。但是,如果我认为out为“你只能读取或返回它”而in为“你只能写或给它赋值”,那么它是有道理的。有人可以解释一下吗?

3 个答案:

答案 0 :(得分:2)

其他答案解决了为什么您不需要在使用网站上出现差异。

仅供参考,如果您将回报转换为预期类型

,API将更有用
@Suppress("UNCHECKED_CAST")
fun <T : Any> Throwable.getCauseIfInstance(e: KClass<T>): T? = when {
    e.java.isAssignableFrom(javaClass) -> this as T
    else -> cause?.getCauseIfInstance(e)
}

但它更像Kotlin一样使用具体类型。

inline fun <reified T : Any> Throwable.getCauseIfInstance(): T? =
    generateSequence(this) { it.cause }.filterIsInstance<T>().firstOrNull()

这实际上与编写显式循环相同,但更短。

inline fun <reified T : Any> Throwable.getCauseIfInstance(): T? {
    var current = this
    while (true) {
        when (current) {
            is T -> return current
            else -> current = current.cause ?: return null
        }
    }
}

与原版不同,此方法不需要kotlin-reflect

(我也将行为从isAssignableFrom更改为isinstanceof);我很难想象原作如何有用。)

答案 1 :(得分:1)

在您的情况下,您实际上并未使用类型参数的方差:您永远不会传递值或使用从e: KClass<T>调用返回的值。

方差描述了您可以作为参数传递的值,以及在处理投影类型时(例如在函数实现中)从属性和函数返回的值可以得到的值。例如,KClass<T>会返回T(如签名中所写),KClass<out SomeType>可以返回SomeType 或其任何子类型。相反,如果KClass<T>期望参数为T,则KClass<in SomeType>期望 SomeType的某些超类型(但不知道确实如此。

事实上,这定义了传递给这样一个函数的实例的实际类型参数的限制。对于不变类型KClass<Base>,您无法传递KClass<Super>KClass<Derived>(其中Derived : Base : Super)。但是如果一个函数需要KClass<out Base>,那么你也可以传递一个KClass<Derived>,因为它满足上述要求:它从其方法返回Derived,它应返回Base或其子类型(但KClass<Super>的情况并非如此)。而且,相反,期望KClass<in Base>的函数也可以接收KClass<Super>

因此,当您重写getCauseIfAssignableFrom以接受e: KClass<in Throwable>时,您声明在实现中您希望能够将Throwable传递给某个泛型函数或{的属性{1}},你需要一个能够处理它的e实例。 KClassAny::class适合,但这不是您需要的。

由于您不会调用任何Throwable::class个功能而无法访问其任何属性,因此您甚至可以将其类型设为e(明确说明你不关心什么是类型并允许它成为任何东西),它会起作用。

但是您的用例要求您将类型限制为KClass<*>的子类型。这是Throwable的工作原理:它将类型参数限制为KClass<out Throwable>的子类型(同样,您声明,对于返回Throwable的{​​{1}}的函数和属性或类似KClass<T> T的内容,您希望使用返回值,就像T是[{1}}的子类型一样;尽管您不这样做)。

适用于您的另一个选项是定义上限Function<T>。这类似于T,但它还捕获Throwable的类型参数,并允许您在签名中的其他位置(在返回类型或其他参数的类型中)或在其中使用它实施

答案 2 :(得分:1)

在您从文档中引用的示例中,您将显示带有out注释的通用。这个注释为类的用户提供了一个保证,即除了从T派生的T或类之外,该类不会输出任何东西。

在您的代码示例中,您将在参数的类型上显示带有out注释的泛型函数参数。这使参数的用户能够保证参数不会是T(在您的情况下为KClass<Throwable>)或从T派生的类(KClass<{derived from Throwable}> })。

现在改变in的想法。如果您使用e: KClass<in Throwable>,则将参数约束为Throwable的超级。

如果您的编译器出错,那么您的函数是否使用e的方法或属性并不重要。在您的情况下,参数的声明限制了函数的调用方式,而不是函数本身如何使用参数。因此,使用in代替out会取消您使用参数NorRemoteRepositoryException::class调用该函数的资格。

当然,约束也适用于你的函数,但是这些约束永远不会被执行,因为e不是那样使用的。