java - kotlin.time.Duration 和 java 反射
问题描述
似乎我在 java 反射 API 的交互方面遇到了一个奇怪的问题(特别是java.lang.reflect.Proxy和kotlin.time.Duration。看起来 Java 反射无法确定 . 的方法返回类型kotlin.time.Duration
。考虑以下示例:
@file:JvmName("Main")
package org.test.kotlin.time.duration
import java.lang.reflect.Proxy
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.seconds
@ExperimentalTime
interface I {
fun getNullableDuration(): Duration? = 1.seconds
fun getRange(): IntRange = 1..3
fun getDuration(): Duration = 1.seconds
}
class IC: I
inline fun <reified T> T.sampleProxy(): T = sampleProxy(T::class.java)
fun <T> sampleProxy(c: Class<T>): T {
return c.cast(Proxy.newProxyInstance(c.classLoader, arrayOf(c)) { _, method, _ ->
println("[proxy] ${method.declaringClass.name}::${method.name} return type is ${method.returnType}")
when (method.name) {
"getNullableDuration", "getDuration" -> 1.seconds
"getRange" -> 0..1
else -> TODO("method ${method.name} isn't handled yet")
}
})
}
fun main() {
val v: I = IC()
println("I::getNullableDuration() return type is ${v::getNullableDuration.returnType}")
v.sampleProxy().getNullableDuration()
println("I::getRange() return type is ${v::getRange.returnType}")
v.sampleProxy().getRange()
println("I::getDuration() return type is ${v::getDuration.returnType}")
v.sampleProxy().getDuration()
}
此代码产生以下输出:
I::getNullableDuration() return type is kotlin.time.Duration?
[proxy] org.test.kotlin.time.duration.I::getNullableDuration return type is class kotlin.time.Duration
I::getRange() return type is kotlin.ranges.IntRange
[proxy] org.test.kotlin.time.duration.I::getRange return type is class kotlin.ranges.IntRange
I::getDuration() return type is kotlin.time.Duration
[proxy] org.test.kotlin.time.duration.I::getDuration return type is double
Exception in thread "main" java.lang.ClassCastException: class kotlin.time.Duration cannot be cast to class java.lang.Double (kotlin.time.Duration is in unnamed module of loader 'app'; java.lang.Double is in module java.base of loader 'bootstrap')
at com.sun.proxy.$Proxy2.getDuration(Unknown Source)
at org.test.kotlin.time.duration.Main.main(main.kt:38)
at org.test.kotlin.time.duration.Main.main(main.kt)
Process finished with exit code 1
可以看到,内部sampleProxy
的返回类型getNullableDuration()
和getRange()
被正确确定(kotlin.time.Duration?
预期变为kotlin.time.Duration
),而getDuration()
突然变成一个返回的方法double
,并且从 kotlin.time.Duration 转换为从该方法返回时加倍失败,但出现异常。
问题的原因可能是什么?我该如何解决?
我的环境:
- OpenJDK 11.0.6+10
- 科特林 1.3.71
- Linux/x86-64
解决方案
这表示:
- Kotlin 编译器将
double
尽可能使用 - 如果它不是包装器类型,将改为使用
从表示部分:
在生成的代码中,Kotlin 编译器为每个内联类保留一个包装器。内联类实例可以在运行时表示为包装器或底层类型。
Kotlin 编译器将更喜欢使用底层类型而不是包装器来生成最高性能和优化的代码。但是,有时需要保留包装器。根据经验,内联类在用作另一种类型时都会被装箱。
在提供的示例中:
getNullableDuration
返回Duration?
这意味着对值进行装箱(参见 Kotlin 文档的表示部分中的示例。getDuration
仅返回Duration
允许编译器使用底层类型double
而不是包装器的值。- 该行
"getNullableDuration", "getDuration" -> 1.seconds
返回一个包装器而不是基础类型,因为它InvocationHandler#invoke
返回一个对象,它不允许编译器使用基础类型。
这就是ClassCastException
抛出的原因。编译后的getDuration
方法返回一个double
,但代理总是返回Duration
。
以下是IC
反编译为 Java 时的样子:
public final class IC implements I {
@Nullable
public Duration getNullableDuration() {
return I.DefaultImpls.getNullableDuration(this);
}
@NotNull
public IntRange getRange() {
return I.DefaultImpls.getRange(this);
}
// vvvvvvvvvv here is the double
public double getDuration() {
return I.DefaultImpls.getDuration(this);
}
}
解决方法
最直接的一种是使用可为空的类型
另一种是返回double
方法getDuration
:
when (method.name) {
"getNullableDuration" -> 1.seconds
"getDuration" -> 1.0
"getRange"-> 0..1
else -> TODO("method ${method.name} isn't handled yet")
}
请注意 time api 和inline
classes 都是实验性的
推荐阅读
- python - 尝试使用 python 将 kdb 转换为 csv,除一列外,所有内容都正确转换
- spring - 当@Controller 中的方法是最终方法时,@Autowired 变量为空
- java - 排序对象的对象
- c++ - 无法使用 Lua 中的 C++ 模块
- ios - 如何将 ViewController 呈现为具有透明背景的弹出式 VC?
- twilio - 想要使用 twilio javascript SDK 创建出站电话会议
- html - 带有post方法的Laravel表单未提交
- sql-server - SSIS - 更改列名的 Excel 到 SQL Server
- jquery - 如何应用 .each 隐藏所有实例,使用 jquery 在 TD 中查找文本
- java - 有没有办法使用 Java 代码而不是外部程序从 XML 模式创建 .java 类?