【问题标题】:How to call Kotlin companion factory method using callBy()?如何使用 callBy() 调用 Kotlin 配套工厂方法?
【发布时间】:2019-02-22 10:44:32
【问题描述】:

如果工厂方法存在,我的代码接受一个类作为参数并准备数据以调用伴随对象工厂方法的该类的构造函数。

调用构造函数时一切正常,但出现错误

java.lang.IllegalArgumentException: No argument provided for a required parameter: instance of fun nz.salect.objjson.JVMTest.StudentWithFactory.Companion.fromJson(kotlin.String, kotlin.Int): nz.salect.objjson.JVMTest.StudentWithFactory

调用工厂方法时。有问题的工厂方法:

data class StudentWithFactory(val name: String, val years: Int=0) {

    companion object {

        fun fromJson(name: String="", age: Int = 0):StudentWithFactory {
            return StudentWithFactory(name, age)
        }
    }
}

没有必需的参数,除非有一些隐藏参数。有什么想法吗?

事实上,我恢复了从fromJson 中完全删除参数并使用::fromJson.callby(emptyMap()) 直接调用伴随方法。同样的错误。

很明显,伴随方法至少需要一个附加参数。也许是班级?还是伴生对象? 如何指定所需的参数?

为建立 callBy() 的函数提供了一个类(或从提供的类中查找该类)以及 json 名称和值。

var funk:KFunction<*>?=null
val companionFuncs=cls.companionObject?.declaredMemberFunctions
if(companionFuncs?.size ?:0 >0){
    companionFuncs?.forEach {
        if(it.name == "fromJson") funk=it
    }

}
val cons:KFunction<T> = if(funk != null)
       funk as KFunction<T>
    else
       cls.primaryConstructor ?: throw IllegalArgumentException("no primary constructor ${cls.simpleName}")
val valuesMap = cons.parameters.filter{it.name in vals}
    .associateBy(
    {it},
    {generateValue(it)}
)
val data = cons.callBy(valuesMap) //as T
return data

【问题讨论】:

  • 请提供调用工厂方法的代码。我假设你使用反射,那么默认参数将不会被使用。
  • 我不是 kotlin 反射专家,但也许您需要 @JvmOverloads 在您的配套工厂方法上,以便字节码中实际存在无参数方法。
  • 你能提供你调用工厂方法的代码吗?我尝试直接调用工厂方法没有问题,但也许我“误解了你的问题......
  • 我认为这是一个类似的问题:stackoverflow.com/questions/48175768/…
  • @Rene 我添加了调用工厂方法的代码。在提供的 json 数据中使用默认参数。

标签: reflection kotlin kotlin-companion


【解决方案1】:

除了我的short answer,更技术性的解释:

是的,实际上有一个隐藏参数,你可以看到它(例如),如果你看一下反编译的(Java)字节码:

public final class StudentWithFactory {

   // ...
   public static final class Companion {
      // ...
      @NotNull
      public static StudentWithFactory fromJson$default(StudentWithFactory.Companion var0, String var1, int var2, int var3, Object var4) {
         // ...

         return var0.fromJson(var1, var2);
      }
      // ...
   }
}

第一个参数(var0)实际上是伴随对象的一个​​实例。 var1 (name) 和 var2 (age) 是您声明的参数。 var3 是一个位掩码,用于确定是否已传递显式值或是否应使用默认值*。老实说,我不知道var4 是干什么用的。它在 Java 代码中未使用。但是导入的部分是,如果要调用该函数,您只需要担心var0var1var2

所以,最终 fromJson* 的非静态版本实际上是在伴随对象的实例上调用的:

var0.fromJson(var1, var2)

*为简单起见,省略了代码

【讨论】:

  • 感谢@Willi,我在周五晚上为我发布了我的问题,并且正在考虑进行类似的实验并使用调试器中的反射数据来验证伴随对象是隐藏参数。由于传递给调用方法的数据是类,通过反射从中找到伴生对象,然后从伴生对象中找到 fromJson() 方法,现在的问题是要传递什么值,我将更新问题
  • @innov8 您可以在我的其他答案中找到原始问题的工作示例。
  • @innov8 不鼓励进行如此大的编辑,因为它们会使已经给出的答案(对原始问题)过时。我的回答完全涵盖了你原来的问题。现在他们没有。请为此创建一个新问题并回滚编辑。
  • 编辑添加了代码,正如几个人要求的那样。该代码进行了“大”编辑,但由于它只是添加人们要求的代码,它澄清了问题。然后我还添加了文本继续关注问题。我已删除并将作为单独的问题发布。我想突出显示您的答案作为参考的正确答案,但建议添加注释,说明构造函数在没有隐藏参数方面是不寻常的
  • @innov8 好的,我明白了! “但建议添加一个注释,即构造函数在没有隐藏参数方面是不寻常的”你是什么意思?
【解决方案2】:

您可以使用parameters 属性来确定您必须将多少参数传递给函数/构造函数。

如果你打电话

val paramsConstr = StudentWithFactory::class.primaryConstructor?.parameters

paramsConstr 将按预期大小为 2,但如果您调用

val paramsFunc = ::fromJson.parameters

paramsFunc 大小为 3。第一个元素对应于伴随对象的实例。所以,这就是您需要提供的参数列表。

您可以像这样调用fromJson

// not using any default parameters
::fromJson.callBy(mapOf(
        paramsFunc[0] to StudentWithFactory::class.companionObjectInstance,
        paramsFunc[1] to "Hello",
        paramsFunc[2] to 30
))

// using only the default parameter for "name"
::fromJson.callBy(mapOf(
        paramsFunc[0] to StudentWithFactory::class.companionObjectInstance,
        paramsFunc[2] to 30
))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多