KFunction
, which is a method / constructor in the reflection of Kotlin
, has various parts abstracted and is easy to use from the application.
On the other hand, compared to the case of directly calling Constructor
/ Method
of Java
, KFunction
has a problem that the call is slow due to the overhead due to abstraction.
In this article, to avoid this overhead, we will summarize the pattern of getting the Constructor
/ Method
of Java
and calling it directly for each definition of KFunction
.
The versions are Kotlin 1.4.10
/ Java8
respectively.
Before getting into the main subject, I will write about kotlin.reflect.KFunction
and java.lang.reflect.Constructor
/ java.lang.reflect.Method
.
As the package name suggests, the former is a function in the reflection of Kotlin
.
In Kotlin
, methods and constructors can be treated as the same KFunction
.
KFunction
can use default arguments by calling callBy
or, under certain conditions, can ignore instance arguments in Method.invoke
of Java
.
The latter is a function in the reflection of Java
.
Since Constructor
and Method
are separated as types, they cannot be treated in the same way, and Method
requires instance parameters ( null
for static method
, and instances otherwise).
The reason I wrote at the beginning, "The call of KFunction
is slower than the case of directly calling Constructor
/ Method
of Java
" does not use the default argument (= all parameters are aligned) This is a comparison between calling KFunction.call
and calling Constructor.newInstance
/ Method.invoke
.
There are roughly two types of function definition methods, and four types in detail, each of which is summarized.
--Constructor --Other than the constructor
@JvmStatic
)The following code was used for verification.
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import kotlin.reflect.KFunction
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.functions
import kotlin.reflect.jvm.javaConstructor
import kotlin.reflect.jvm.javaMethod
@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
data class ConstructorSample(val foo: Int, val bar: String)
fun instanceMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
companion object {
fun companionObjectFunctionSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
@JvmStatic
fun staticMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
}
val expected = ConstructorSample(1, "2")
@Test
@DisplayName("For constructor")
fun constructorTest() {
val function: KFunction<ConstructorSample> = ::ConstructorSample
assertEquals(expected, function.call(1, "2"))
val javaConstructor: Constructor<ConstructorSample> = function.javaConstructor!!
assertEquals(expected, javaConstructor.newInstance(1, "2"))
}
@Test
@DisplayName("For instance functions")
fun instanceMethodTest() {
val function: KFunction<ConstructorSample> = this::instanceMethodSample
assertEquals(expected, function.call(1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(this, 1, "2"))
}
@Nested
@DisplayName("For methods defined on companion objects")
inner class CompanionObjectFunctionTest {
@Test
@DisplayName("When obtained by method reference")
fun byMethodReferenceTest() {
val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::companionObjectFunctionSample
//No instance parameters required when retrieved with method reference
assertEquals(expected, function.call(1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
}
@Test
@DisplayName("When acquired by reflection")
fun byReflectionTest() {
val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
.functions.first { it.name == "companionObjectFunctionSample" }
.let { it as KFunction<ConstructorSample> }
//Instance parameter required when acquired by reflection
assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
}
}
@Nested
@DisplayName("For the method defined in the companion object (with JvmStatic specified)")
inner class StaticMethodTest {
@Test
@DisplayName("When obtained by method reference")
fun byMethodReferenceTest() {
val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::staticMethodSample
assertEquals(expected, function.call(1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
}
@Test
@DisplayName("When obtained by reflection from a companion object")
fun byReflectionTest() {
val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
.functions.first { it.name == "staticMethodSample" }
.let { it as KFunction<ConstructorSample> }
//Instance parameter required when acquired by reflection
assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
}
}
}
If the generator of KFunction
is a constructor, you can getConstructor
with KFunction.javaConstructor
.
Since the constructor does not require instance parameters etc., you can call Constructor.newInstance
in the same way as KFunction.call
.
@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
data class ConstructorSample(val foo: Int, val bar: String)
val expected = ConstructorSample(1, "2")
@Test
@DisplayName("For constructor")
fun constructorTest() {
val function: KFunction<ConstructorSample> = ::ConstructorSample
assertEquals(expected, function.call(1, "2"))
val javaConstructor: Constructor<ConstructorSample> = function.javaConstructor!!
assertEquals(expected, javaConstructor.newInstance(1, "2"))
}
}
If the generator of KFunction
is other than the constructor, you can getMethod
with KFunction.javaMethod
.
As mentioned above, you need an instance parameter (null
for static method
, instance otherwise) to call Method
.
Here, in cases other than the constructor, the method of acquiring an instance from KFunction
is not disclosed regardless of the generation method, so even if the instance parameter is omitted in KFunction.call
, from KFunction
alone. You cannot call Method.invoke
[^ instance_hosoku].
Even if you add JvmStatic
, you cannot call Method.invoke
with the instance parameter as null
because it is the definition to companion object
that can be acquired by the method of acquiring as KFunction
.
[^ instance_hosoku]: As long as the instance parameter can be omitted in KFunction.call
, it is considered that the instance information is held internally in some way, but it is exposed in the interface of KFunction
. Since KFunctionImpl
, which is an implementation class of KFunction
, is a ʻinternal` class, we judge that it will be true black magic if we chase it any further, and here we conclude that we cannot.
Therefore, in order to call Method
from KFunction
obtained from other than the constructor, it is necessary to prepare an instance separately.
@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
data class ConstructorSample(val foo: Int, val bar: String)
fun instanceMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
val expected = ConstructorSample(1, "2")
@Test
@DisplayName("For instance functions")
fun instanceMethodTest() {
val function: KFunction<ConstructorSample> = this::instanceMethodSample
assertEquals(expected, function.call(1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(this, 1, "2"))
}
}
@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
data class ConstructorSample(val foo: Int, val bar: String)
companion object {
fun companionObjectFunctionSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
}
val expected = ConstructorSample(1, "2")
@Nested
@DisplayName("For methods defined on companion objects")
inner class CompanionObjectFunctionTest {
@Test
@DisplayName("When obtained by method reference")
fun byMethodReferenceTest() {
val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::companionObjectFunctionSample
//No instance parameters required when retrieved with method reference
assertEquals(expected, function.call(1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
}
@Test
@DisplayName("When acquired by reflection")
fun byReflectionTest() {
val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
.functions.first { it.name == "companionObjectFunctionSample" }
.let { it as KFunction<ConstructorSample> }
//Instance parameter required when acquired by reflection
assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
}
}
}
@JvmStatic
) [^ static_hosoku][^ static_hosoku]: As summarized in this article, the functions defined on Kotlin
cannot be obtained by static Functions
, so in this example It is omitted.
@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
data class ConstructorSample(val foo: Int, val bar: String)
companion object {
@JvmStatic
fun staticMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
}
val expected = ConstructorSample(1, "2")
@Nested
@DisplayName("For the method defined in the companion object (with JvmStatic specified)")
inner class StaticMethodTest {
@Test
@DisplayName("When obtained by method reference")
fun byMethodReferenceTest() {
val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::staticMethodSample
assertEquals(expected, function.call(1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
}
@Test
@DisplayName("When obtained by reflection from a companion object")
fun byReflectionTest() {
val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
.functions.first { it.name == "staticMethodSample" }
.let { it as KFunction<ConstructorSample> }
//Instance parameter required when acquired by reflection
assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))
val javaMethod: Method = function.javaMethod!!
assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
}
}
}
In this article, in order to avoid the call overhead of KFunction
, we have summarized the pattern to getConstructor
/ Method
of Java
for each definition of KFunction
and call it directly.
As a result of verification, it was concluded that in the case of a constructor, Constructor.newInstance
can be called from a single KFunction
, and in other cases, Method.invoke
can be called only if the instance can be obtained separately. I did.
It seems that it is easiest to use KFunction.callBy
if you just want to make a tool that works, but if you want to make a tool regardless of execution speed, you can call Constructor
/ Method
directly. You may try it.
I hope this article has helped you.
Recommended Posts