我想在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表示:
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
则为“您只能为其写入或分配值”,那么这是有道理的。有人可以解释吗? 最佳答案
在您的情况下,实际上并没有使用type参数的方差:您永远不会传递值或使用从对e: KClass<T>
的调用返回的值。
方差描述了在使用投影类型时(例如,在函数实现内部),您可以将哪些值作为参数传递,以及期望从属性和函数返回的值中得到什么。例如,如果KClass<T>
将返回T
(如签名中所写),则KClass<out SomeType>
可以返回SomeType
或其任何子类型。相反,在KClass<T>
期望有T
参数的情况下,KClass<in SomeType>
期望有SomeType
的某些父类(super class)型(但确切地未知)。
实际上,这定义了传递给此类函数的实例的实际类型参数的限制。对于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
,但这不是您所需要的。
由于您不调用e
的任何函数且不访问其任何属性,因此您甚至可以将其类型设置为KClass<*>
(明确指出您不在乎什么类型,并且可以将其作为任何内容),它会工作。
但是您的用例要求您将类型限制为Throwable
的子类型。这是KClass<out Throwable>
起作用的地方:它将type参数限制为Throwable
的子类型(同样,您声明,对于返回KClass<T>
或带有T
的诸如T
的Function<T>
的函数和属性,您想要使用返回值,就像T
是Throwable
的子类型;尽管您没有这样做)。
适合您的另一个选项是定义上限<T : Throwable>
。这类似于<out Throwable>
,但是它还捕获了KClass<T>
的type参数,并允许您在签名中的其他位置(返回类型或其他参数的类型)或实现内部使用它。