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