我剛剛做了一個奇怪的發現,想知道為什么它會這樣作業。以下代碼引發編譯器錯誤:
interface A
class B: A
val mapOfA: Map<A,A>
val mapOfB = mapOf<B,B>()
mapOfA = mapOfB
你得到
Type mismatch.
Required: Map<A, A>
Found: Map<B, B>
但是這段代碼有效。
val mapOfA: Map<A,A>
val mapOfB = mapOf<B,B>()
mapOfA = mapOfB.toMap()
唯一的區別是現在我打電話給mapOfB.toMap(). mapOfB已經是一個Map那么為什么這會改變什么?我正在使用 Kotlin 版本1.5.10。這里發生了什么?
uj5u.com熱心網友回復:
考慮mapOfB.get。這接受 aB并且只接受aB。
這是完全有可能的實作mapOfB是不能支持get(A),有沒有它。例如,想象B是Int,并且A是Number。ImaginemapOfB實際上是根據陣列實作的。 mapOfA.get(3.14159)當然不能Int在陣列中查找非鍵,因為陣列是由Ints索引的。
(科特林選擇這種設計相比于Java的設計,我不相信是明智之舉-但是這是他們選擇什么Java的選擇是對get,containsKey等取的Object說法,這導致了這樣的問題這樣。 )
這在 的定義中特別指定Map<K, out V>:V允許向上轉換,但不允許K。
uj5u.com熱心網友回復:
這與型別variance有關。考慮這個例子:
val mapOfA: Map<A,A>
val mapOfB = mapOf<B,B>()
mapOfA = mapOfB // assume this is allowed
val item = mapOfA.get(A())
我們在這里做了一些奇怪的事情。兩個變數都指向同一個地圖,所以我們只是要求mapOfB它的A專案。但mapOfB對A密鑰并不真正了解。它應該與B鑰匙一起使用。它需要B在它的get(),但我們提供了A。因此,我們剛剛打破了型別安全。這就是為什么這是不允許的。
但為什么能toMap()正常作業?因為它創建了地圖的副本。現在,要求mapOfA對A關鍵要求僅此副本,而不是地圖B的。所以這是允許的。
uj5u.com熱心網友回復:
Map 鍵的型別是不變的。這意味著 aMap<B, B>不是 aMap<A, B>或者Map<A, A>因為你不能向上轉換一個不變型別。從理論上講,當傳遞錯誤型別的鍵時,正在使用的 Map 介面的實作可能會崩潰,就像你傳遞給它的 A 的某個子型別不是 B 一樣。
當您呼叫 時toMap,它會創建一個新的 Map,已知使用超型別 A 作為 Key 是安全的,因此它可以安全地向上轉換型別。在幕后,它正在將每個條目轉移到一個新地圖,因此它基本上是將每個鍵向上轉換為 type A。
以下是型別安全保護您免受的示例:
interface A
class B(val name: String): A
class C: A
class MyMap: HashMap<B, B>() {
override fun get(key: B): B? {
println("I'm returning ${key.name}")
return super.get(key)
}
}
如果您現在這樣做并且編譯器讓您:
val a = Map<A, A>
val b: Map<B, B> = MyMap()
a = b // imagine this is allowed.
val x = a[C()] // Crash. C cannot be cast to B inside the MyMap.get() function
如果你使用toMap(),一個新的 Map 是從頭開始創建的,它不會有這個問題,所以編譯器向上轉換鍵型別是安全的。
Java 沒有這個問題,因為getandcontains等不接受鍵型別的引數型別,而是接受任何東西。這兩種方法各有利弊。它們都可以保護您免受不同型別的錯誤。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/325169.html
上一篇:如何在字典中使用多個標準
下一篇:Terraform匯入地圖資源
