[Kotlin] Get Java Constructor / Method from KFunction and call it

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.

Preface

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.

Text

There are roughly two types of function definition methods, and four types in detail, each of which is summarized.

--Constructor --Other than the constructor

The following code was used for verification.

The whole program 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"))
        }
    }
}

For constructor

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"))
    }
}

Other than the constructor

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.

For instance functions

@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"))
    }
}

For functions defined on companion objects

@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"))
        }
    }
}

For companion objects (with @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"))
        }
    }
}

Summary

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

[Kotlin] Get Java Constructor / Method from KFunction and call it
[Java] Get KFunction from Method / Constructor in Java [Kotlin]
Call a method with a Kotlin callback block from Java
Write a class in Kotlin and call it in Java
Implement Java Interface in JRuby class and call it from Java
Call Java method from JavaScript executed in Java
[Java] Get Json from URL and handle it with standard API (javax.script)
[Android] Call Kotlin's default argument method from Java
Java method call from RPG (method call in own class)
Call Java from JRuby
JAVA constructor call processing
Java language from the perspective of Kotlin and C #
[Java] [Kotlin] Generically call valueOf and values of Enum
[Android development] Get an image from the server in Java and set it in ImageView! !!
[Java] Get metadata from files with Apathce Tika, and get image / video width and height from metadata [Kotlin]
Memorandum No.4 "Get a character string and decorate it" [Java]
[Java] Get Charset with Apathce Tika / Initialize String from Charset [Kotlin]
Get video information from Nikorepo and throw it to Slack
Java methods and method overloads
Get attributes and values from an XML file in Java
Search and execute method by name from instance with processing (java)
Install the memcached plugin on MySQL and access it from Java
Notes on building Kotlin development environment and migrating from Java to Kotlin
[Java] How to convert from String to Path type and get the path
Call Kotlin's sealed class from Java
Differences between "beginner" Java and Kotlin
Get country from IP address (Java)
[Java beginner] == operator and equals method
Java overload constructor starting from beginner
About go get and go install from Go1.16
Call TensorFlow Java API from Scala
Call the super method in Java
[Java] Pass arguments to constructor in Mockito / Set method default call to callRealMethod
JSON in Java and Jackson Part ③ Embed JSON in HTML and use it from JavaScript
Gradle automatically generates version number from git and uses it in Java
Choose the first non-empty one from multiple Optional and call that method
[Java] Get MimeType from the contents of the file with Apathce Tika [Kotlin]
Conversion between Kotlin nullable and Java Optional
Java starting from beginner, variables and types
Java Silver exam procedure and learning method
[Kotlin] 3 ways to get Class from KClass
Call Java library from C with JNI
Get caller information from stack trace (java)
[For beginners] Difference between Java and Kotlin
[Java] Get tag information from music files
A Java engineer compared Swift, Kotlin, and Java.
[Java] Collection and StringBuilder operation method comparison
Directory change monitoring method memo (Java, Kotlin)
Call Java methods from Nim using jnim
Get history from Zabbix server in Java
[Java] Instance method, instance field, class method, class field, constructor summary
[Java8] Search the directory and get the file
Memo for migration from java to kotlin
[Java] TreeMap successor method and entrySet complexity
[Java] Get and display the date 10 days later using the Time API added from Java 8.
[Java] Program example to get the maximum and minimum values from an array