我有一個用例,我需要創建從 KClass 到 (lambda) 函式的映射映射,該函式將該類的實體轉換為其他內容(在我的情況下為字串)。在java中,我會寫一些類似的東西:
private Map<Class<?>, Function<?, String>> mappers = new HashMap<>();
在科特林,對于拉姆達引數的語法應該是(Something...) -> ReturnType,但是這并不接受或者*,也in還是out。請注意,地圖應該能夠包含任何采用指定型別或更具體的任何引數的映射器,以便最寬松(但是,由于這是動態的,因此不會被驗證。了解這一點很重要鑄造目的)。
這是如何正確表達的?
我最后是怎么解決的
感謝@broot 和@Sweeper,我能夠實作我的用例。因為我遇到了一些其他問題,并且我假設發現此問題的任何人都有類似的用例,所以我想添加有問題的其余代碼。我最終采用了以下方法(減少到它的必需品):
// note that this map is public because it is accessed by a public
// inline function. Making it private won't compile.
val mappers = HashMap<KClass<*>, (Any) -> String>()
// this is used to add mappers to the map of mappers.
// note the noinline!
inline fun <reified T> mapping (noinline mapper: (T) -> String) {
mappers[T::class] = mapper as (Any) -> String
}
// This is the only method accessing the map to extract mappers and
// directly use them.
fun mapToString(obj: Any): String {
// some stuff here...
// Attempt to map to the id via a predefined mapper.
candidate = mappers[obj::class]?.let { it.invoke(obj) }
if (candidate != null) return candidate
// some other fallback here...
}
另請注意,以上所有內容都嵌套在另一個類中,為了引數呼叫,我將使用該類Cache。這是單元測驗:
@Test
fun `test simple extractor`() {
class SomeClass(val somethingToExtract: String)
val someInstance = SomeClass("someValue")
val cache = Cache()
// defining the extractor
cache.mapping<SomeClass> { it.somethingToExtract }
// using the extractor
val id = cache.mapToString(someInstance)
assertEquals(id, someInstance.somethingToExtract)
}
uj5u.com熱心網友回復:
這種操作在 Java 和 Kotlin 中默認是不允許的,因為它不是型別安全的。問題是你可以拿一個接收整數的函式作為例子,存盤在映射中,然后使用它傳遞一個字串給它。
我們可以通過執行未經檢查的強制轉換來強制編譯器禁用型別保證。我們需要使用Any代替*:
private val mappers = mutableMapOf<KClass<*>, (Any) -> String>()
fun main() {
mappers[User::class] = ::getUserName as (Any) -> String // unchecked cast
val john = User("John")
val username = mappers[User::class]?.invoke(john)
println(username) // John
}
fun getUserName(user: User): String = user.name
data class User(val name: String)
然后我們必須確保正確使用型別。可能最好的方法是將此映射包裝在我們自己的實用程式中,該實用程式對型別執行運行時檢查以提供型別安全。
uj5u.com熱心網友回復:
你的 Java 型別寫得有點奇怪。具體來說,函式的型別是:
Function<? extends Object, String>
請注意,?與? extends Object. 這會破壞PECS,除了傳遞null給它的apply方法之外,您將無法安全地傳遞任何東西。
的第一個型別引數Function是函式接受(消耗)的型別,根據PECS,它應該標記為super(逆變),而不是extends(協變):
Function<? super Object, String>
如果您對第二個型別引數也遵循 PECS,它將是Function<? super Object, ? extends String>.
現在,Kotlin 一開始就不允許您破壞 PECS。Kotlin 中的每個函式型別都自動具有逆變引數型別和協變回傳型別。這就是為什么您可以將 a 分配(Any) -> String給 a(String) -> Any而無需任何強制轉換。
Kotlin 函式型別等效于Function<? super Object, ? extends String>:
(Any) -> String
或(Any?) -> String取決于您喜歡可空值的程度。
您應該讓您的地圖將其中一種型別作為值型別,并且由于這會改變方差,因此您需要更改進行未經檢查的強制轉換的位置,但這應該很簡單。
正如 broot 在評論中提醒我的那樣,Kotlin 有一種NothingJava 沒有的型別。這是所有型別的子型別(與所有型別Any的超型別相比)。當您嘗試在 Kotlin 中破壞 PECS 時,您可以看到這一點,例如嘗試呼叫applya Function<*, String>,您會看到它apply需要一個Nothing.
因此,您可以寫(Nothing) -> String來代表Function<?, String>,但我不建議這樣做。“一個將‘無’作為引數的函式”有點難以閱讀和混淆。它是否帶引數?:D
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/395725.html
上一篇:如果沒有找到A,我如何選擇B?
