kotlin.time.Duration and java reflection



我似乎在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到方法返回时加倍的强制转换失败,但有例外。

出现问题的原因是什么?如何解决此问题?

我的环境:

  • OpenJDK11.0.6+10
  • Kotlin 1.3.71
  • Linux/x86-64

Durationdoubleinline类。

这意味着:

  • Kotlin编译器将在可能的情况下使用double
  • 如果它不是包装类型,则将使用

来自表示部分:

在生成的代码中,Kotlin编译器为每个内联类保留一个包装器。内联类实例可以在运行时表示为包装器或底层类型。

Kotlin编译器更喜欢使用底层类型而不是包装器来生成最具性能和优化的代码但是,有时有必要保留包装。根据经验,每当内联类用作另一种类型时,它们都会被装箱

在提供的示例中:

  • getNullableDuration返回Duration?,这意味着对值进行装箱(请参阅Kotlin文档的表示部分中的示例(
  • getDuration只返回Duration,这允许编译器使用底层类型double而不是包装器
  • "getNullableDuration", "getDuration" -> 1.seconds返回一个包装器而不是底层类型,因为InvocationHandler#invoke返回一个不允许编译器使用底层类型的Object

这就是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);
}
}

解决方法

最简单的方法是使用可为null的类型

另一种是为getDuration方法返回double

when (method.name) {
"getNullableDuration" -> 1.seconds
"getDuration" -> 1.0
"getRange"-> 0..1
else -> TODO("method ${method.name} isn't handled yet")
}

请注意,时间api和inline类都是实验性

最新更新