Вызов функции со значениями параметров по умолчанию с использованием отражения - PullRequest
2 голосов
/ 02 мая 2019

Рассмотрим интерфейс:

interface Foo {
    fun func(argWithDefault: String = "default")
}

и его (минимальную) реализацию:

class FooImpl() : Foo {
    override fun func(argWithDefault: String) {
        println(argWithDefault)
    }
}

Я тоже пытаюсь вызвать func на FooImpl через отражение:

val func = Foo::func
val instance = FooImpl()
func.callBy(mapOf(func.instanceParameter!! to instance))

но я получаю следующее исключение:

kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Этот вызываемый объект не поддерживает вызов по умолчанию: public abstract fun func (argWithDefault: kotlin.String = ...): kotlin.Unit, определенный в com.example.Foo [DeserializedSimpleFunctionDescriptor @ 2d7275fc]

Foo::func.parameters[1].isOptional возвращает true и, согласно документам, isOptionalвозвращает

true, если этот параметр является необязательным и может быть опущен при выполнении вызова через KCallable.callBy, или false в противном случае.

, поэтому я предполагаю, что это должно быть возможно.

1 Ответ

1 голос
/ 02 мая 2019

tl; dr: это, кажется, известная проблема: KT-13936: KotlinReflectionInternalError при вызове callBy для переопределенного члена с унаследованным значением аргумента по умолчанию

Некоторыеанализ, который я делал раньше:

Если мы посмотрим на то, что на самом деле генерирует Kotlin, становится очевидным, почему у вас больше нет значений по умолчанию.Байт-код Foo:

public abstract interface Foo {

  public abstract func(Ljava/lang/String;)V
    LOCALVARIABLE this LFoo; L0 L1 0
    LOCALVARIABLE argWithDefault Ljava/lang/String; L0 L1 1

  public final static INNERCLASS Foo$DefaultImpls Foo DefaultImpls
}


// ================Foo$DefaultImpls.class =================
  public static synthetic func$default(LFoo;Ljava/lang/String;ILjava/lang/Object;)V
    ALOAD 3
    IFNULL L0
    NEW java/lang/UnsupportedOperationException
    DUP
    LDC "Super calls with default arguments not supported in this target, function: func"
    INVOKESPECIAL java/lang/UnsupportedOperationException.<init> (Ljava/lang/String;)V
    ATHROW
   L0
    ILOAD 2
    ICONST_1
    IAND
    IFEQ L1
   L2
    LINENUMBER 2 L2
    LDC "default" // <--- here is our default value? but where are we? Foo$Defaults.func$default?
    ASTORE 1
   L1
    ALOAD 0
    ALOAD 1
    INVOKEINTERFACE Foo.func (Ljava/lang/String;)V (itf)
    RETURN
    MAXSTACK = 3
    MAXLOCALS = 4

  public final static INNERCLASS Foo$DefaultImpls Foo DefaultImpls
  // compiled from: Foo.kt
}

Таким образом, объявленная функция в Foo - это просто fun foo(String) ... Никакое значение по умолчанию нигде, кроме синтетической функции в ее собственном DefaultImpls -классе.

Так, где эта функция называется ... FooImpl может быть?

Если мы посмотрим на FooImpl -байт-код, становится ясно, что его там нет:

public final class FooImpl implements Foo {

  public func(Ljava/lang/String;)V
   L0
    ALOAD 1
    LDC "argWithDefault"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 3 L1
   L2
    ICONST_0
    ISTORE 2
   L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L4
   L5
    LINENUMBER 4 L5
    RETURN
   L6
    LOCALVARIABLE this LFooImpl; L0 L6 0
    LOCALVARIABLE argWithDefault Ljava/lang/String; L0 L6 1
    MAXSTACK = 2
    MAXLOCALS = 3

...

Для полноты картины я также создал Caller.kt, содержащий следующий код:

fun main() = FooImpl().func()

и, наконец, еще раз байт-код, но также наш DefaultImpls.func$default -call:

public final class CallerKt {


  public final static main()V
   L0
    LINENUMBER 1 L0
    NEW FooImpl
    DUP
    INVOKESPECIAL FooImpl.<init> ()V
    ACONST_NULL
    ICONST_1
    ACONST_NULL
    INVOKESTATIC Foo$DefaultImpls.func$default (LFoo;Ljava/lang/String;ILjava/lang/Object;)V // aha! here is our DefaultImpls.func$default!
    RETURN
   L1
    MAXSTACK = 4
    MAXLOCALS = 0

Итак ... поскольку DefaultImpls только что сгенерирован компилятором и используется при необходимости, утилиты reflect могут (и, на мой взгляд, должны) отражать этот факт тоже ... Я добавил похожую известную проблемуотносительно открытых классов (которые также используют синтетические ...$default -функции) в верхней части ответа.Если эта проблема не полностью решает вашу проблему, вы можете открыть вместо нее Kotlin .

Просто немного поигрались со связанной проблемой ... на самом деле образец тамбудет работать, если вместо B::foo используется A::foo (A - открытый класс и B расширяет A).Однако, поскольку A в основном будет похож на ваш Foo -интерфейс, интерфейс, тем не менее, требует особого подхода.

...