Kotlinのdata classのpropertyをreflectionで更新する
Kotlinのdata classのpropertyをreflectionで更新する方法を調べてみたメモです。
Kotlinのdata classのpropertyをreflectionで更新する必要があったので、方法を調べてみました。
KotlinではJavaのreflection APIとkotlinのreflection API両方使えるので両方で試してみました。
下記バージョンで試してみます。
- Kotlin 1.3.72
Dependency
gradleを使って試してみます。
kotlin1.3ではreflection APIは標準で含まれてないため、kotlin-reflectをdependencyを追加します。
build.gradle
dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlin:kotlin-reflect") }
data class
Kotlinでデータを保持するクラスを作成する際、よくdata classを使用してます。
data classはvalでプロパティを宣言して、immutableな構造にすることがほとんどです。
data classにはcopy()という便利なメソッドが生えてるので、
data classのデータを更新する際は copy()で新しいインスタンスを生成します。
data class Person( val name: String, val age: Int ) fun main() { val person = Person( name = "Alice", age = 20 ) val aged = person.copy( age = 21 ) println("aged: $aged") }
結果
aged: Person(name=Alice, age=21)
普通はcopy()を使っておけば困ることはないのですが、
reflectionで更新する必要があったので方法を調べてみました。
varのproperty
下記のようなvarでpropertyが定義されたdata classの場合。
data class MutablePerson( var name: String, var age: Int )
KMutableProperty
kotlinのreflectionを使って変更してみます。
KClassのmemberPropertiesから、KMutablePropertyを取得して変更出来ます。
Main.kt
fun main() { val mutable = MutablePerson( "Anna", 20 ) println("before: $mutable") val ageProperty = mutable::class.memberProperties .first { it.name == "age" } as KMutableProperty<*> ageProperty.isAccessible = true ageProperty.setter.call(mutable, 21) println("after : $mutable") }
結果
before: MutablePerson(name=Anna, age=20) after : MutablePerson(name=Anna, age=21)
Method
Javaのreflectionで更新してみます。
Class.getMethod()でセッターのMethodを取得して更新出来ます。
fun main() { val mutable = MutablePerson( "Bob", 30 ) println("before: $mutable") val setter = mutable::class.java.getMethod("setAge", Int::class.java) // val setter = mutable.javaClass.getMethod("setAge", Int::class.java) setter.invoke(mutable, 33) println("after : $mutable") }
結果
before: MutablePerson(name=Bob, age=30) after : MutablePerson(name=Bob, age=33)
Field
もしくはClass.getDeclaredField()でFieldを取得して更新出来ます。
fun main() { val mutable = MutablePerson( "Cindy", 40 ) println("before: $mutable") val age = mutable::class.java.getDeclaredField("age") age.isAccessible = true age.set(mutable, 44) println("after : $mutable") }
結果
before: MutablePerson(name=Cindy, age=40) after : MutablePerson(name=Cindy, age=44)
valのproperty
下記のようなvarでpropertyが定義されたdata classの場合。
data class RealOnlyPerson( val name: String, val age: Int )
KProperty
Kotlinのreflectionで更新は出来ませんでした。
valで定義されたpropertyの場合、KClass.memberPropertiesで取得出来るのはKProperty1なので、
setterは存在しないので更新出来ません。
fun main() { val readOnly = RealOnlyPerson( "Anna", 20 ) println("before: $readOnly") val ageProperty = readOnly::class.memberProperties .first { it.name == "age" } as KProperty<*> ageProperty.isAccessible = true // Can't call setter method because it's not declared // ageProperty.setter.call(readOnly, 21) println("after : $readOnly") }
Method
Javaのreflectionで更新してみます。
mutableなdata classの時のようにClass.getMethod()でセッターのMethodを取得しようとしても取得出来ません。
fun main() { // error!! /* val readOnly = RealOnlyPerson( "Bob", 30 ) println("before: $readOnly") val setter = readOnly::class.java.getMethod("setAge", Int::class.java) setter.invoke(readOnly, 33) println("after : $readOnly") */ }
結果
Exception in thread "main" java.lang.NoSuchMethodException: com.example.kotlin.reflection.dataclassproperty.RealOnlyPerson.setAge(int) at java.base/java.lang.Class.getMethod(Class.java:2108) at com.example.kotlin.reflection.dataclassproperty.MainKt.main(Main.kt:97) at com.example.kotlin.reflection.dataclassproperty.MainKt.main(Main.kt)
理由は単純で、RealOnlyPersonをDecompileしてみると、
valで定義されたpropertyの場合、getterはありますがsetterは存在しません。
(varで定義すると存在している)
public final class RealOnlyPerson { @NotNull private final String name; private final int age; @NotNull public final String getName() { return this.name; } public final int getAge() { return this.age; } public RealOnlyPerson(@NotNull String name, int age) { Intrinsics.checkParameterIsNotNull(name, "name"); super(); this.name = name; this.age = age; } /* 〜略〜 */ }
Field
結果として、valで定義されたpropertyの場合、
Class.getDeclaredField()でFieldを取得して更新出来ます。
fun main() { val readOnly = RealOnlyPerson( "Cindy", 40 ) println("before: $readOnly") val age = readOnly::class.java.getDeclaredField("age") age.isAccessible = true age.set(readOnly, 44) println("after : $readOnly") }
結果
before: RealOnlyPerson(name=Cindy, age=40) after : RealOnlyPerson(name=Cindy, age=44)
サンプルコードは下記にあげました
https://github.com/pppurple/kotlin_examples/blob/master/kotlin-reflection-example/src/main/kotlin/com/example/kotlin/reflection/dataclassproperty/Main.kt
【参考】
https://stackoverflow.com/questions/58360868/how-to-change-a-kotlin-private-val-using-reflection
https://stackoverflow.com/questions/35525122/kotlin-data-class-how-to-read-the-value-of-property-if-i-dont-know-its-name-at
https://stackoverflow.com/questions/44304480/how-to-set-delegated-property-value-by-reflection-in-kotlin
https://stackoverflow.com/questions/52512458/how-to-set-val-property-with-kotlin-reflection
https://stackoverflow.com/questions/57090841/set-property-by-string-in-kotlin-using-reflection
おわり。