kotlin - 使用反射调用具有默认参数值的函数

标签 kotlin kotlin-reflect

考虑一个界面:

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

及其(最小)实现:

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

我也尝试通过反射在 FooImpl 上调用 func:

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

但我得到以下异常:

kotlin.reflect.jvm.internal.KotlinReflectionInternalError: This callable does not support a default call: public abstract fun func(argWithDefault: kotlin.String = ...): kotlin.Unit defined in com.example.Foo[DeserializedSimpleFunctionDescriptor@2d7275fc]

Foo::func.parameters[1].isOptional 返回 true,并且根据文档,isOptional 返回

true if this parameter is optional and can be omitted when making a call via KCallable.callBy, or false otherwise.

所以我想这应该是可能的。

最佳答案

tl;dr:这似乎是一个已知问题:KT-13936: KotlinReflectionInternalError on invoking callBy on overridden member with inherited default argument value

我之前做过的一些分析:

如果我们看看 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 实用程序也可以(并且在我看来应该)反射(reflect)这一事实...我在答案的顶部添加了一个关于开放类(也使用合成 ...$default 函数)的类似已知问题。如果该问题没有完全解决您的问题,您可能需要打开一个新的 Kotlin issue相反。

只是稍微研究一下链接的问题...实际上,如果使用 A::foo 而不是 B::foo ( >A 是开放类,B 扩展 A)。然而,由于 A 基本上与您的 Foo 接口(interface)类似,因此该接口(interface)仍然需要对此进行特殊处理。

关于kotlin - 使用反射调用具有默认参数值的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55948868/

相关文章:

java - Android - Service、IntentService、JobIntentService - 如果应用程序被杀死,它们就会停止

android - 编译器发生异常 (1.8.0_301)

android - 我不能在 kotlin 中使用 DataBindingUtil.setContentView()

kotlin - 使用Kotlin-reflect设置可为空的UShort

kotlin - 对属性 setter 的方法引用

java - 在 Java 代码中使用 kotlin 库

android - Rx Java Android 中的混淆

kotlin - 从 kotlin 中的嵌套类型参数获取类

kotlin - Kotlin |查找使用lambda返回lambda的函数的参数