我想在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
为“你只能写或给它赋值”,那么它是有道理的。有人可以解释一下吗?
答案 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
更改为is
(instanceof
);我很难想象原作如何有用。)
答案 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
实例。 KClass
或Any::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
不是那样使用的。