Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin invoke getter/setter reflectively

Beginner in Kotlin here.

I try to create and populate objects by reflection in a program. I cannot find the equivalent functionality in pure kotlin so my solution resembles the code below which works fine, but requires the use of dirty references like java.lang.String::class.java and intelliJ, understandably, doesn't seem to like this. Is there a simpler way that I am missing to do this?

val jclass = myObject::class.java 
val setters = jclass.declaredMethods.filter { it.name.startsWith("set") }
for (s in setters) {
    val paramType = s.parameterTypes.first()
    val data = when(paramType) {
        java.lang.Integer::class.java -> foo
        java.lang.Double::class.java -> bar
        java.lang.String::class.java -> baz
    }
    s.invoke(myObject, data)
}
like image 956
kliron Avatar asked Dec 17 '16 21:12

kliron


People also ask

How to use getters and setters in Kotlin?

In Kotlin, setter is used to set the value of any variable and getter is used to get the value. Getters and Setters are auto-generated in the code. Let’s define a property ‘ name ‘, in a class, ‘ Company ‘. The data type of ‘ name ‘ is String and we shall initialize it with some default value. The above code is equivalent to this code:

How to call getter and setter using reflection in Java?

There are two ways to invoke getter and setter using reflection in java. You can use PropertyDescriptor to call getters and setters using reflection. Setter: Call getWriteMethod () on PropertyDescriptor.

What is Kotlin reflection in JVM?

On the JVM platform, standard library contains extensions for reflection classes that provide a mapping to and from Java reflection objects (see package kotlin.reflect.jvm ). For example, to find a backing field or a Java method that serves as a getter for a Kotlin property, you can write something like this:

How to declare a property in Kotlin?

In Kotlin, we can define properties in the same way as we declare another variable. Kotlin properties can be declared either as mutable using the var keyword or as immutable using the val keyword. Here, the property initializer, getter and setter are optional. We can also omit the property type, if it can be inferred from the initializer.


2 Answers

You can use Kotlin reflection, which requires you to add kotlin-reflect as a dependency to your project.

Here you can find kotlin-reflect for Kotlin 1.0.5, or pick another version if you use different Kotlin version.

After that, you can rewrite your code as follows:

val properties = myObject.javaClass.kotlin.memberProperties
for (p in properties.filterIsInstance<KMutableProperty<*>>()) {
    val data = when (p.returnType.javaType) {
        Int::class.javaPrimitiveType,
        Int::class.javaObjectType -> foo
        Double::class.javaPrimitiveType,
        Double::class.javaObjectType -> bar
        String::class.java -> baz
        else -> null
    }
    if (data != null)
        p.setter.call(myObject, data)
}

Some details:

  • Despite using Kotlin reflection, this approach works with Java classes as well, their fields and accessors will be seen as properties, as described here.

  • Just like with Java reflection, memberProperties returns public properties of this type and all its supertypes. To get all the properties declared in the type (including the private ones, but not those from the supertypes), use declaredMemberProperties instead.

  • .filterIsInstance<KMutableProperty<*> returns only the mutable properties, so that you can use their p.setter later. If you need to iterate over the getters of all the properties, remove it.

  • In the when block, I compared p.returnType.javaType to Int::class.javaPrimitiveType and Int::class.javaObjectType, because what's Int in Kotlin can be mapped to either Java int or java.lang.Integer depending on its usage. In Kotlin 1.1, it will be enough to check p.returnType.classifier == Int::class.

like image 177
hotkey Avatar answered Dec 09 '22 04:12

hotkey


If You need to get property getter/setter, there is a couple of built-in constructions for it YourClass::propertyName

have a look at example bellow

fun main(args: Array<String>) {
        val myObject = Cat("Tom", 3, 35)
        println(Cat::age.getter.call(myObject)) // will print 3
        Cat::age.setter.call(myObject, 45)
        print(myObject) // will print Cat(name=Tom, age=45, height=35)
    }

    data class Cat(var name : String, var age : Int, val height : Int)

but sometimes you don't know class exactly(working with generics) or need to get list of properties, then use val <T : Any> KClass<T>.declaredMemberProperties: Collection<KProperty1<T, *>> it will return all properties, some of them can be mutable(var) and some immutable(val), you can find out immutability by checking belonging to KMutableProperty<*> (by filtering with is operator or using convenience methods such as filterIsInstance<KMutableProperty<*>>)

about your code snippet

I absolutely agree with hotkey, but now it is better to use myObject::class.declaredMemberProperties instead of myObject.javaClass.kotlin.memberProperties

because the second one is deprecated

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/java-class.html

     data class Cat(var name : String, var age : Int, val height : Int)

     @JvmStatic
            fun main(args: Array<String>) {
                val myObject = Cat("Tom", 3, 35)
                val properties = myObject::class.declaredMemberProperties
                for (p in properties.filterIsInstance<KMutableProperty<*>>()) {
                    val data = when (p.returnType.javaType) {
                        Int::class.javaPrimitiveType,
                        Int::class.javaObjectType -> 5
                        String::class.java -> "Rob"
                        else -> null
                    }
                    if (data != null)
                        p.setter.call(myObject, data)
                }
                println(myObject)
                // it will print Cat(name=Rob, age=5, height=35),
                // because height isn't var(immutable)
            }

in general, I would approach similar problems with such construction in mind

val myObject = Cat("Tom", 3, 35)

Cat::class.declaredMemberProperties
                    //if we want only public ones
                    .filter{ it.visibility == KVisibility.PUBLIC }
                    // We only want strings
                    .filter{ it.returnType.isSubtypeOf(String::class.starProjectedType) }
                    .filterIsInstance<KMutableProperty<*>>()
                    .forEach { prop ->
                        prop.setter.call(myObject, "Rob")
                    }

println(myObject)
//it will print Cat(name=Rob, age=3, height=35),
//because name is only eligible in this case
like image 40
Alex Avatar answered Dec 09 '22 04:12

Alex