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)
}
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:
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.
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:
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.
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
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With