我想在 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
为"you can only read from, or return, it"和 in
为"you can only write, or assign a value, to it",那么它是有道理的 . 谁能解释一下?
3 回答
在您的情况下,您实际上并未使用类型参数的方差:您永远不会传递值或使用从调用
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
传递给某个泛型函数或e
的属性,并且您需要一个能够处理它的KClass
实例 .Any::class
或Throwable::class
适合,但这不是你需要的 .由于你没有调用它的任何属性,你甚至可以使它的类型
KClass<*>
(明确说明你不关心什么是类型并允许它成为任何东西),它会起作用 .但是您的用例要求您将类型限制为
Throwable
的子类型 . 这是KClass<out Throwable>
的工作原理:它将类型参数限制为Throwable
的子类型(同样,您声明,对于KClass<T>
的函数和属性,返回T
或T
的内容,如Function<T>
,您希望使用返回值,就好像T
是Throwable
的子类型;虽然你没有这样做) .适用于您的另一个选项是定义上限
<T : Throwable>
. 这类似于<out Throwable>
,但它还捕获KClass<T>
的类型参数,并允许您在签名中的其他位置(在返回类型或其他参数的类型中)或实现内部使用它 .其他答案已经解决了为什么你不需要在这个 use 网站上的差异 .
仅供参考,如果您将回报转换为预期类型,API将更有用,
但是使用一种具体的类型更像Kotlin .
这实际上与编写显式循环相同,但更短 .
与原版不同,此方法不需要
kotlin-reflect
.(我也将行为从
isAssignableFrom
更改为is
(instanceof
);我很难想象原作如何有用 . )在您从文档中引用的示例中,您将显示带有
out
注释的通用 class . 这个注释为类的用户提供了一个保证,即除了从T派生的T或类之外,该类不会放出任何东西 .在您的代码示例中,您将在参数的类型上显示带有
out
注释的泛型函数 parameter . 这给参数的用户保证参数不会是T
(在你的情况下是aKClass<Throwable>
)或派生自T
(aKClass<{derived from Throwable}>
)的类 .现在改变
in
的想法 . 如果您要使用e: KClass<in Throwable>
,则将参数约束为Throwable
的超级 .在编译器错误的情况下,您的函数是否使用
e
的方法或属性并不重要 . 在您的情况下,参数的声明限制了函数的调用方式,而不是函数本身如何使用参数 . 因此,使用in
而不是out
将使用参数NorRemoteRepositoryException::class
取消对函数的调用 .当然,约束也适用于您的函数,但这些约束永远不会被执行,因为
e
不是那样使用的 .