[Kotlin] Obtenez le constructeur / la méthode Java de KFunction et appelez-le

«KFunction», qui est une méthode / constructeur dans le reflet de «Kotlin», a plusieurs parties abstraites et est facile à utiliser depuis l'application. D'un autre côté, comparé au cas de l'appel direct de Constructor / Method de Java, KFunction a un problème que l'appel est lent en raison de la surcharge due à l'abstraction.

Dans cet article, pour éviter cette surcharge, nous allons résumer le modèle pour obtenir le Constructor / Method de Java et l'appeler directement par la définition de la KFunction. Les versions sont respectivement Kotlin 1.4.10 / Java8.

Préface

Avant d'entrer dans le sujet principal, j'écrirai sur kotlin.reflect.KFunction et java.lang.reflect.Constructor / java.lang.reflect.Method.

Comme le nom du paquet l'indique, le premier est une fonction dans le reflet de Kotlin. Dans «Kotlin», les méthodes et les constructeurs peuvent être traités comme la même «KFunction». KFunction peut utiliser l'argument par défaut en appelant callBy, ou peut ignorer l'argument d'instance dans Method.invoke de Java sous certaines conditions.

Ce dernier est une fonction dans la réflexion «Java». Puisque «Constructor» et «Method» sont séparés en tant que types, ils ne peuvent pas être traités de la même manière, et «Method» requiert des paramètres d'instance («null» pour «static method», et les instances autrement).

La raison pour laquelle j'ai écrit au début, "L'appel de KFunction est plus lent que le cas de l'appel direct de Constructor / Method de Java" n'utilise pas l'argument par défaut (= tous les paramètres sont alignés) Il s'agit d'une comparaison entre l'appel de KFunction.call et l'appel de Constructor.newInstance / Method.invoke.

Texte

Il existe à peu près deux types de méthodes de définition de fonction et quatre types en détail, dont chacun est résumé.

--Constructeur --Autre que le constructeur

Le code suivant a été utilisé pour la vérification.

Programme global utilisé pour la vérification
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("Pour les constructeurs")
    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("Par exemple les fonctions")
    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("Pour les méthodes définies sur les objets compagnons")
    inner class CompanionObjectFunctionTest {
        @Test
        @DisplayName("Lorsque obtenu par référence de méthode")
        fun byMethodReferenceTest() {
            val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::companionObjectFunctionSample
            //Aucun paramètre d'instance requis lors de la récupération avec la référence de méthode
            assertEquals(expected, function.call(1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }

        @Test
        @DisplayName("Une fois acquis par réflexion")
        fun byReflectionTest() {
            val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
                .functions.first { it.name == "companionObjectFunctionSample" }
                .let { it as KFunction<ConstructorSample> }
            //Paramètres d'instance requis lors de l'acquisition par réflexion
            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("Dans le cas de la méthode définie dans l'objet compagnon (avec JvmStatic spécifié)")
    inner class StaticMethodTest {
        @Test
        @DisplayName("Lorsque obtenu par référence de méthode")
        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("Lorsqu'elle est obtenue par réflexion d'un objet compagnon")
        fun byReflectionTest() {
            val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
                .functions.first { it.name == "staticMethodSample" }
                .let { it as KFunction<ConstructorSample> }
            //Paramètres d'instance requis lors de l'acquisition par réflexion
            assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }
    }
}

Pour les constructeurs

Si la source de KFunction est un constructeur, vous pouvez obtenirConstructor avec KFunction.javaConstructor. Puisque le constructeur ne requiert aucun paramètre d'instance, etc., vous pouvez appeler Constructor.newInstance de la même manière que KFunction.call.

@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
    data class ConstructorSample(val foo: Int, val bar: String)

    val expected = ConstructorSample(1, "2")

    @Test
    @DisplayName("Pour les constructeurs")
    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"))
    }
}

Autre que le constructeur

Si la source de KFunction est autre que le constructeur, vous pouvez obtenirMethod avec KFunction.javaMethod. Comme mentionné ci-dessus, vous avez besoin d'un paramètre d'instance («null» pour «méthode statique», sinon une instance) pour appeler «Method».

Ici, dans les cas autres que le constructeur, la méthode d'acquisition d'une instance de «KFunction» n'est pas divulguée quelle que soit la méthode de génération, donc même si le paramètre d'instance est omis dans «KFunction.call», de «KFunction» uniquement. Vous ne pouvez pas appeler Method.invoke [^ instance_hosoku]. Même si vous ajoutez «JvmStatic», vous ne pouvez pas appeler «Method.invoke» avec le paramètre d'instance comme «null» car c'est la définition de «l'objet compagnon» qui peut être acquise par la méthode d'acquisition en tant que «KFunction».

[^ instance_hosoku]: Tant que le paramètre d'instance peut être omis dans KFunction.call, on considère que les informations d'instance sont conservées en interne d'une certaine manière, mais elles sont exposées dans l'interface de KFunction. Puisque KFunctionImpl, qui est une classe d'implémentation de KFunction, est une classe ʻinternal`, il est jugé que si vous la poursuivez plus loin, cela deviendra une véritable magie noire, et ici nous concluons que cela ne peut être fait.

Par conséquent, afin d'appeler «Method» à partir de «KFunction» obtenue à partir d'un autre constructeur que le constructeur, il est nécessaire de préparer une instance séparément.

Par exemple les fonctions

@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("Par exemple les fonctions")
    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"))
    }
}

Pour les fonctions définies sur les objets compagnons

@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("Pour les méthodes définies sur les objets compagnons")
    inner class CompanionObjectFunctionTest {
        @Test
        @DisplayName("Lorsque obtenu par référence de méthode")
        fun byMethodReferenceTest() {
            val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::companionObjectFunctionSample
            //Aucun paramètre d'instance requis lors de la récupération avec la référence de méthode
            assertEquals(expected, function.call(1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }

        @Test
        @DisplayName("Une fois acquis par réflexion")
        fun byReflectionTest() {
            val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
                .functions.first { it.name == "companionObjectFunctionSample" }
                .let { it as KFunction<ConstructorSample> }
            //Paramètres d'instance requis lors de l'acquisition par réflexion
            assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }
    }
}

Pour les objets compagnons (avec @ JvmStatic) [^ static_hosoku]

[^ static_hosoku]: Comme résumé dans cet article, les fonctions définies sur Kotlin ne peuvent pas être obtenues par static Functions, donc dans cet exemple Il est omis.

@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("Dans le cas de la méthode définie dans l'objet compagnon (avec JvmStatic spécifié)")
    inner class StaticMethodTest {
        @Test
        @DisplayName("Lorsque obtenu par référence de méthode")
        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("Lorsqu'elle est obtenue par réflexion d'un objet compagnon")
        fun byReflectionTest() {
            val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
                .functions.first { it.name == "staticMethodSample" }
                .let { it as KFunction<ConstructorSample> }
            //Paramètres d'instance requis lors de l'acquisition par réflexion
            assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }
    }
}

Résumé

Dans cet article, afin d'éviter la surcharge d'appel de KFunction, nous avons résumé le modèle pour obtenirConstructor / Method of Java par la définition de KFunction et l'appelons directement. À la suite de la vérification, il a été conclu que dans le cas d'un constructeur, Constructor.newInstance peut être appelé à partir d'une seule KFunction, et dans d'autres cas, Method.invoke ne peut être appelé que si l'instance peut être obtenue séparément. J'ai fait.

Il semble qu'il soit plus facile d'utiliser KFunction.callBy si vous voulez juste créer un outil qui fonctionne, mais si vous voulez créer un outil quelle que soit la vitesse d'exécution, vous pouvez appeler Constructor / Method directement. Vous pouvez l'essayer.

J'espère que cet article vous a aidé.

Recommended Posts

[Kotlin] Obtenez le constructeur / la méthode Java de KFunction et appelez-le
[Java] Obtenir KFunction à partir de la méthode / du constructeur en Java [Kotlin]
Appeler une méthode avec le bloc de rappel de Kotlin depuis Java
Ecrire une classe en Kotlin et l'appeler en Java
Implémentez l'interface Java dans la classe JRuby et appelez-la depuis Java
Appel de méthodes Java à partir de JavaScript exécutées en Java
[Java] Obtenir et gérer Json à partir d'une URL avec une API standard (javax.script)
[Android] Appeler la méthode d'argument par défaut de Kotlin depuis Java
Appel de méthode Java depuis RPG (appel de méthode dans sa propre classe)
Appeler Java depuis JRuby
Traitement des appels du constructeur JAVA
Langage Java du point de vue de Kotlin et C #
[Java] [Kotlin] Appeler valueOf et les valeurs de Enum de manière générique
[Développement Android] Obtenez des images du serveur avec Java et définissez-les dans ImageView! !!
[Java] Obtenez des métadonnées à partir de fichiers avec Apathce Tika, et obtenez la largeur et la hauteur des images / vidéos à partir des métadonnées [Kotlin]
Mémorandum n ° 4 "Obtenez une chaîne de caractères et décorez-la" [Java]
[Java] Obtenir le jeu de caractères avec Apathce Tika / Initialiser la chaîne à partir du jeu de caractères [Kotlin]
Obtenez des informations vidéo de Nikorepo et envoyez-les à Slack
Méthodes Java et surcharges de méthodes
Obtenir des attributs et des valeurs à partir d'un fichier XML en Java
Rechercher et exécuter une méthode à partir d'une instance avec traitement (java)
Installez le plugin memcached sur MySQL et accédez à partir de Java
Remarques sur la création de l'environnement de développement de Kotlin et la migration de Java vers Kotlin
[Java] Comment convertir du type String en type Path et obtenir le chemin
Appeler la classe scellée de Kotlin depuis Java
Différences entre Java "débutant" et Kotlin
Obtenir le pays à partir de l'adresse IP (Java)
[Java débutant] == opérateur et méthode equals
Java, constructeur de surcharge à partir du débutant
Appelez l'API Java de TensorFlow depuis Scala
Appelez la super méthode en Java
[Java] Passer des arguments au constructeur dans Mockito / Définir l'appel par défaut à la méthode à callRealMethod
JSON en Java et Jackson Part ③ Incorporer JSON dans HTML et l'utiliser à partir de JavaScript
Gradle génère automatiquement le numéro de version de git et l'utilise en Java
Sélectionnez le premier non vide parmi plusieurs facultatifs et appelez sa méthode
[Java] Récupère MimeType à partir du contenu du fichier avec Apathce Tika [Kotlin]
Conversion entre Kotlin nullable et Java facultative
Java pour les débutants, les variables et les types
Comment passer l'examen Java Silver et comment apprendre
[Kotlin] Trois façons d'obtenir un cours depuis KClass
Appeler la bibliothèque Java à partir de C avec JNI
Obtenir des informations sur l'appelant à partir de la trace de la pile (Java)
[Pour les débutants] Différence entre Java et Kotlin
[Java] Obtenir des informations sur les balises à partir de fichiers musicaux
Un ingénieur Java a comparé Swift, Kotlin et Java.
[Java] Comparaison des méthodes d'opération de la collection et de StringBuilder
Mémo de méthode de surveillance des changements de répertoire (Java, Kotlin)
Obtenir l'historique du serveur Zabbix en Java
[Java] Méthode d'instance, champ d'instance, méthode de classe, champ de classe, résumé du constructeur
[Java8] Recherchez le répertoire et récupérez le fichier
Mémo pour la migration de Java vers Kotlin
[Java] Méthode successeur de TreeMap et complexité de l'entréeSet
[Java] Obtenez et affichez la date 10 jours plus tard à l'aide de l'API Time ajoutée à partir de Java 8.
[Java] Exemple de programme qui acquiert les valeurs maximum et minimum d'un tableau