2017-08-08 55 views
3

我想创建一个Throwable扩展函数,给定一个KClass,递归搜索与参数匹配的根本原因。下面是一个尝试的作品:Kotlin:泛型和变异

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约泛型说:

这就是所谓的声明站点变化:我们可以标注来源的 类型参数T,以确保(生产)它只返回 从成员来源,并从未消费。要做到这一点,我们提供 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

嘿,只注意到你的递归函数为总理候选人一个序列:'generateSequence(this){it.cause} .first {it :: class.java.isAssignableFrom(e.java)}'应该是行为兼容的,IMO会更快地消耗精神上的力量,并且也会摆脱递归。不要编辑问题,因为它可能会使当前答案无效。 –

+0

你用你的方法调用Throwable的派生类,所以当你将参数定义为KClass <在Throwable>中时,你应该期望编译错误,对吗? – Les

+0

@ mEQ5aNLrK3lqs3kfSa5HbvsTWe0nIu一些用户名!关于顺序,我看到的问题是首先收集所有原因,然后返回匹配的原因(如果有的话)。但是如果我们找到一个匹配,则不需要重复;你声称序列比递归更快似乎理论上并不成立。 –

回答

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传递给一些通用的功能或e的财产,并且您需要能够处理该问题的KClass实例。 Any::classThrowable::class会适合,但这不是你所需要的。

由于您不会调用e的任何功能,也不会访问其任何属性,所以您甚至可以将其类型设置为KClass<*>(明确指出您不在乎什么类型并允许它是什么),它会工作。

但是您的用例要求您将类型限制为Throwable的子类型。这是KClass<out Throwable>的作用:它将类型参数限制为Throwable的子类型(同样,您声明,对于KClass<T>返回TTFunction<T>的函数和属性,您希望使用返回值TThrowable的子类型;尽管您不这样做)。

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

+0

我3次阅读你的解释,你说的是真的,但我不确定它是否回答我的问题。正如你所说,除了获得java类以外,我不使用'e'的任何属性或方法。这似乎也应该工作,但事实并非如此。你能帮我理解为什么不呢? –

+0

@AbhijitSarkar,这是因为当你定义一个类型为“KClass 类型”的参数时,你可以限制你可以传递的参数,这样它们都应该能够接收'Throwable',其中'KClass ''T' '(你希望能够通过**中的Throwable **)。例如,Any :: class可以解决这个限制:因为它可以将'Any'作为'T'来接收,它也可以接收'Throwable'。但'NoRemoteRepositoryException :: class'不会:它只期望'NoRemoteRepositoryException'及其子类型为'T',所以它不知道如何处理任意'Throwable'。 – hotkey

+0

答案的冗长实际上是对我理解的一个障碍:)我更喜欢简短的和现实的答案。但我明白每个人都有自己的解释和理解方式。 –

1

在您从文档中引用的示例中,您将显示带有out注释的。这个注解给类的用户保证该类不会拿出任何东西,但来自T.

得出在你的代码示例T或类,你是显示一个泛型函数参数out注释上参数的类型。这是给参数的用户保证参数不会是除了T(在你的情况下是KClass<Throwable>)或从T(aKClass<{derived from Throwable}>)派生的类。

现在扭转思考in。如果您要使用e: KClass<in Throwable>,那么您将参数限制为Throwable的超级参数。

对于编译器错误,您的函数是否使用e的方法或属性并不重要。在你的情况下,参数的声明限制了函数如何被调用,而不是函数本身如何使用参数。因此,使用in而不是out将取消您对具有参数NorRemoteRepositoryException::class的功能的调用资格。

当然,约束也适用于您的功能,但这些约束永远不会被执行,因为e不以这种方式使用。

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) 
} 

但它更科特林样使用物化型。

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

(我也改变了行为从isAssignableFromisinstanceof);我也很难想象如何原来可能是有用的。)

+0

对于带有特定类型参数并且没有高阶函数参数的内联函数,最好将逻辑抽取为带有额外Java Class的非内联函数,然后使inliine函数简单地调用该函数。这将防止字节码爆炸,如果功能逻辑增长并从许多地方使用。 –

+0

实现中的'e'形式参数在哪里? –

+1

@AbhijitSarkar没有必要。当你编写'.getCauseIfInstance ()'时,编译器会填充其余部分。 – ephemient