Kotlin 相当于 java 的 Function<*, String>
Kotlin equivalent to java's Function<*, String>
我有一个用例,我需要创建一个从 KClass 到 (lambda) 函数的映射映射,该函数将那个 class 的实例转换为其他东西(在我的例子中是一个字符串)。在 java 中,我会按照以下方式写一些东西:
private Map<Class<?>, Function<?, String>> mappers = new HashMap<>();
在 Kotlin 中,lambda 参数的语法应该是 (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...
}
另请注意,以上所有内容都嵌套在另一个 class 中,为了参数调用 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)
}
默认情况下,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)
然后我们必须确保正确使用类型。可能最好的办法是将此映射包装在我们自己的实用程序中,该实用程序对类型执行运行时检查以提供类型安全。
你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 中的每个函数类型自动具有逆变参数类型和协变 return 类型。这就是为什么您可以将 (Any) -> String
分配给 (String) -> Any
而无需任何转换。
Function<? super Object, ? extends String>
等价的 Kotlin 函数类型是:
(Any) -> String
或 (Any?) -> String
取决于您对可空值的喜欢程度。
您应该使您的地图具有这些类型之一作为值类型,并且由于这会改变方差,您将需要更改进行未检查转换的位置,但这应该很简单。
正如 broot 在评论中提醒我的那样,Kotlin 有一个 Nothing
类型而 Java 没有。这是所有类型的子类型(将其与 Any
- 所有类型的超类型进行比较)。当您尝试在 Kotlin 中破坏 PECS 时,您会看到这一点,例如尝试在 Function<*, String>
上调用 apply
,您会看到 apply
需要 Nothing
.
因此,你可以写(Nothing) -> String
来表示Function<?, String>
,但我不建议这样做。 “一个以 'nothing' 作为参数的函数”有点难以阅读和混淆。它是否需要参数? :D
我有一个用例,我需要创建一个从 KClass 到 (lambda) 函数的映射映射,该函数将那个 class 的实例转换为其他东西(在我的例子中是一个字符串)。在 java 中,我会按照以下方式写一些东西:
private Map<Class<?>, Function<?, String>> mappers = new HashMap<>();
在 Kotlin 中,lambda 参数的语法应该是 (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...
}
另请注意,以上所有内容都嵌套在另一个 class 中,为了参数调用 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)
}
默认情况下,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)
然后我们必须确保正确使用类型。可能最好的办法是将此映射包装在我们自己的实用程序中,该实用程序对类型执行运行时检查以提供类型安全。
你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 中的每个函数类型自动具有逆变参数类型和协变 return 类型。这就是为什么您可以将 (Any) -> String
分配给 (String) -> Any
而无需任何转换。
Function<? super Object, ? extends String>
等价的 Kotlin 函数类型是:
(Any) -> String
或 (Any?) -> String
取决于您对可空值的喜欢程度。
您应该使您的地图具有这些类型之一作为值类型,并且由于这会改变方差,您将需要更改进行未检查转换的位置,但这应该很简单。
正如 broot 在评论中提醒我的那样,Kotlin 有一个 Nothing
类型而 Java 没有。这是所有类型的子类型(将其与 Any
- 所有类型的超类型进行比较)。当您尝试在 Kotlin 中破坏 PECS 时,您会看到这一点,例如尝试在 Function<*, String>
上调用 apply
,您会看到 apply
需要 Nothing
.
因此,你可以写(Nothing) -> String
来表示Function<?, String>
,但我不建议这样做。 “一个以 'nothing' 作为参数的函数”有点难以阅读和混淆。它是否需要参数? :D