kotlin .time. Продолжительность и java отражение - PullRequest
4 голосов
/ 25 апреля 2020

Кажется, я столкнулся со странной проблемой с взаимодействием java API отражения (в частности, java .lang.reflect.Proxy и kotlin .time.Duration . Похоже, Java Reflection не может определить тип возврата метода для 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. Продолжительность удвоения при возврате из метода завершается с ошибкой, за исключением.

В чем может быть причина проблемы? Как я могу ее обойти?

Моя среда:

  • OpenJDK 11.0.6 + 10
  • Kotlin 1.3.71
  • Linux / x86-64

1 Ответ

2 голосов
/ 25 апреля 2020

Duration - это класс inline для double.

Это означает:

  • Kotlin компилятор будет использовать double там, где это возможно
  • , где он не является типом оболочки, будет использоваться вместо

Из раздела представление :

В сгенерированном коде компилятор Kotlin сохраняет оболочку для каждого встроенного класса. Встроенные экземпляры классов могут быть представлены во время выполнения как в виде оберток или как базовый тип.

Компилятор Kotlin предпочтет использовать базовые типы вместо оберток для получения наиболее производительного и оптимизированного кода. Однако иногда необходимо держать обертки вокруг. Как правило, встроенные классы упаковываются, когда они используются в качестве другого типа.

В представленном примере:

  • getNullableDuration возвращает Duration? что означает упаковку значения (см. пример в разделе представление документации Kotlin.
  • getDuration возвращает просто Duration, что позволяет компилятору использовать базовый тип, double вместо оболочки.
  • строка "getNullableDuration", "getDuration" -> 1.seconds возвращает оболочку вместо базового типа, потому что InvocationHandler#invoke возвращает объект, который не позволяет компилятору использовать базовый type.

Вот почему выбрасывается 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);
   }
}

Обходные пути

Самый простой способ - использовать nullable type

Другой способ - вернуть double для метода getDuration:

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

Обратите внимание, что классы времени api и inline экспериментальные

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...