考虑一个界面:
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 = ...):com.example.Foo中定义的kotlin.Unit [DeserializedSimpleFunctionDescriptor @ 2d7275fc]
Foo::func.parameters[1].isOptional
返回true
,并且根据文档,isOptional
返回
如果此参数是可选的,则为true,并且在通过KCallable.callBy进行呼叫时可以省略,否则为false。
所以我想这应该可行。
答案 0 :(得分:1)
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
调用:
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 issue。
稍微处理一下链接的问题……实际上,如果使用A::foo
而不是B::foo
(A
是开放类,{{1} }扩展了B
)。但是,由于A
基本上与您的A
接口类似,因此该接口仍需要特殊对待。