2
votes

Comment convertir une extension en membre?

Kotlin vous permet d'étendre des instances concrètes d'une classe typée génériquement. Par exemple, supposons que j'ai la classe suivante Foo , avec les fonctions d'extension Foo .bar () et Foo .bar () code>:

class Foo<T>(t: T) {
  val a: String = "bar"
  val b: Int = 111
}

fun Foo<Int>.bar() = a
fun Foo<String>.bar() = b

fun main(args: Array<String>) {
  val stringBar: String = Foo(1).bar()
  val intBar: Int = Foo("").bar()
}

Est-il possible d'obtenir ce comportement sans fonctions d'extension, et si oui, comment convertir les fonctions d'extension en membres? Est-ce possible sans renommer ou modifier les signatures de type?


0 commentaires

3 Réponses :


1
votes

Vous pouvez créer une fonction membre en ligne avec le paramètre générique réifié :

class Foo<T>(t: T) {
    val a: String = "bar"
    val b: Int = 111

    inline fun <reified T, E> member(): E? {
        var r: E? = null
        when(T::class) {
            String::class -> r = b as E
            Int::class -> r = a as E
        }
        return r
    }
}

fun main(args: Array<String>) {
   val str = Foo(1).member<Int, String>()
   val i = Foo("").member<String, Int>()
}

Mais les fonctions d'extension sont mieux dans votre cas: ils sont sécurisés pour le type, plus concis et lisibles.


3 commentaires

Votre code permet également d'appeler par ex. Foo (1.0) .member () ce qui n'est pas souhaitable.


Le premier paramètre générique est censé être un type de classe qui est vérifié dans l'expression when , le type de deuxième classe du résultat renvoyé. Si nous spécifions File comme deuxième type générique, il doit être géré dans la fonction, sinon Exception sera levée.


"Destiné à", mais il n'y a aucun moyen d'appliquer ou même de vérifier cela. Par exemple. vous avez val x = Foo (1) pour commencer, mais ensuite modifier à 1.0, vous devez vous rappeler de modifier également tous les appels de méthode. Dans le code d'origine, ce n'est pas le cas, le compilateur vous le dira. Et le deuxième paramètre réussit ici à être moins utile que de renvoyer Any (IMO).



1
votes

Non, ce n'est pas possible (la réponse actuellement acceptée ne se comporte pas du tout de la même manière que votre code).


5 commentaires

Alors, comment Kotlin implémente-t-il des extensions pour pouvoir distribuer en fonction d'un type générique (qui est normalement effacé), et comment Kotlin met-il des extensions sur des types de données génériques à la disposition des consommateurs Java, c'est-à-dire qu'est-ce qui rend les extensions spéciales ici?


Comme la surcharge normale, la méthode est sélectionnée au moment de la compilation, avant que quoi que ce soit ne soit effacé. Pour les consommateurs Java, les extensions ne sont que des méthodes prenant le récepteur comme argument. En raison des restrictions JVM, ils auront des noms différents dans ce cas.


Désolé pour la dernière phrase trompeuse du commentaire précédent. Les types de retour étant différents après l'effacement, cette surcharge est autorisée par JVM. Je ne me souviens pas si Java lui-même le permet.


Oui, la JVM autorise deux fonctions qui ne diffèrent que par le type de retour dans la même portée, mais pas Java. Voir ce fil de discussion: stackoverflow.com/questions/ 42916801 /…


Ce que je voulais dire, c'est une combinaison de types d'arguments différents avant l'effacement et de types de retour après. Les raisons normales de ne pas autoriser la surcharge uniquement sur les types de retour ne s'appliquent pas. Mais oui, Java ne le permet pas ( ideone.com/pgyRCx ) alors que Kotlin le fait, que ce soit pour les méthodes d'extension ou pas.



1
votes

En utilisant la réflexion, vous n'avez pas à faire le mappage entre le type et la propriété par vous-même.

La fonction membre propertyValue () renverra la valeur de la première propriété qui a le type de le paramètre générique ou null .

class Foo {
    val a: String = "bar"
    val b: Int = 111

    inline fun <reified T> propertyValue() = Foo::class.memberProperties.firstOrNull {
        it.returnType == T::class.createType()
    }?.get(this)
}

Foo().propertyValue<Int>()) // 111
Foo().propertyValue<String>()) // bar
Foo().propertyValue<Double>()) // null

Vous pouvez bien sûr étendre cette fonction pour qu'elle vous donne les valeurs de toutes les propriétés du type souhaité T (si vous avez plusieurs Int propriétés par exemple).


0 commentaires