首页 > 解决方案 > kotlin.time.Duration 和 java 反射

问题描述

似乎我在 java 反射 API 的交互方面遇到了一个奇怪的问题(特别是java.lang.reflect.Proxykotlin.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 转换为从该方法返回时加倍失败,但出现异常。

问题的原因可能是什么?我该如何解决?

我的环境:

标签: javakotlinreflection

解决方案


Duration是一个inlinedouble

这表示:

  • 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 和inlineclasses 都是实验性的


推荐阅读