4 votes

Kotlin renvoie le même objet à partir de la méthode Factory

Je joue avec Kotlin et j'ai trouvé un comportement intéressant. Donc, disons que je veux avoir une sorte d'usine :

internal interface SomeStupidInterface {
    companion object FACTORY {
       fun createNew(): ChangeListener {
            val time = System.currentTimeMillis()
            return ChangeListener { element -> Log.e("J2KO", "time " + time) }
        }

        fun createTheSame(): ChangeListener {
            return ChangeListener { element -> Log.e("J2KO", "time " + System.currentTimeMillis()) }
        }
    }

    fun notifyChanged()
}

ChangeListener défini dans le fichier Java :

interface ChangeListener {
    void notifyChange(Object element);
}

Ensuite, j'essaie de l'utiliser à partir de Java, comme suit :

ChangeListener a = SomeStupidInterface.FACTORY.createNew();
ChangeListener b = SomeStupidInterface.FACTORY.createNew();
ChangeListener c = SomeStupidInterface.FACTORY.createTheSame();
ChangeListener d = SomeStupidInterface.FACTORY.createTheSame();
Log.e("J2KO", "createNew a == b -> " + (a == b));
Log.e("J2KO", "createTheSame c == d -> " + (c == d));

Les résultats sont les suivants :

createNew: a == b -> false
createTheSame: c == d -> true

Je comprends pourquoi createNew renvoie de nouveaux objets en raison de la fermeture. Mais pourquoi je reçois la même instance de createTheSame méthode ?

P.S. Je sais que le code ci-dessus n'est pas idiomatique :)

6voto

Niek Haarman Points 13931

Il s'agit d'une question de performance. Créer moins d'objets est évidemment plus performant, et c'est ce que Kotlin essaie de faire.

Pour chaque lambda, Kotlin génère une classe qui implémente l'interface appropriée. Ainsi, par exemple, le code Kotlin suivant :

fun create() : () -> Unit {
  return { println("Hello, World!") }
}

correspond à quelque chose comme :

Function0 create() {
  return create$1.INSTANCE;
}

final class create$1 implements Function0 {

  static final create$1 INSTANCE = new create$1();

  void invoke() {
    System.out.println("Hello, World!");
  }
} 

Vous pouvez voir ici que c'est toujours la même instance qui est renvoyée.


Cependant, si vous faites référence à une variable qui se trouve en dehors de la portée de lamdba, cela ne fonctionnera pas : il n'y a aucun moyen pour l'instance singleton d'accéder à cette variable.

fun create(text: String) : () -> Unit {
  return { println(text) }
}

Au lieu de cela, pour chaque invocation de create une nouvelle instance de la classe doit être instanciée, qui a accès à l'élément text variable :

Function0 create(String text) {
  return new create$1(text);
}

final class create$1 implements Function0 {

  final String text;

  create$1(String text) {
    this.text = text;
  }

  void invoke() {
    System.out.println(text);
  }
} 

C'est pourquoi votre a et b sont les mêmes, mais les c et d ne le sont pas.

4voto

zsmb13 Points 36441

Première remarque : votre code d'exemple ne fonctionne pas tel quel : l'interface doit être écrite en Java pour pouvoir être utilisée avec les constructeurs SAM.

Pour ce qui est de la question proprement dite, vous avez déjà évoqué les raisons de ce comportement. Les lambdas (dans ce cas, les constructeurs SAM) sont compilés en classes anonymes (à moins qu'ils ne soient inlinés). S'ils capturent des variables externes, une nouvelle instance de la classe anonyme sera créée à chaque invocation. Sinon, puisqu'ils n'ont pas besoin d'avoir d'état, une seule instance sera créée à chaque invocation de la lambda. Je suppose que c'est pour des raisons de performance, à défaut d'autre chose. (Remerciements à l'équipe de Kotlin en action pour les informations contenues dans ce paragraphe).

Si vous souhaitez renvoyer une nouvelle instance à chaque fois sans capturer de variables, vous pouvez utiliser l'option complète object notation :

fun createNotQUiteTheSame(): ChangeListener {
    return object : ChangeListener {
        override fun notifyChanged(element: Any?) {
            println("time " + System.currentTimeMillis())
        }
    }
}

Le fait d'appeler plusieurs fois la fonction ci-dessus renverra des instances différentes à chaque appel. Il est intéressant de noter qu'IntelliJ suggère de convertir cette fonction en utilisant la syntaxe de conversion SAM d'origine :

fun createNotQUiteTheSame(): ChangeListener {
    return ChangeListener { println("time " + System.currentTimeMillis()) }
}

qui, comme vous l'avez déjà constaté, renvoie la même instance à chaque fois.

Je suppose que cette conversion est proposée parce que la comparaison de l'égalité de ces instances sans état est un cas très particulier. Si vous avez besoin de pouvoir faire une comparaison entre les instances qui sont retournées, il est probablement préférable d'utiliser la version complète de object notation. Ensuite, vous pouvez même ajouter un état supplémentaire à chaque auditeur, sous la forme d'un élément id par exemple.

-1voto

Silvestr Points 474

Il semble que vous essayez d'utiliser Conversion SAM avec l'interface Kotlin.

Notez que les conversions SAM ne fonctionnent que pour les interfaces, pas pour les classes abstraites, même si celles-ci n'ont qu'une seule méthode abstraite.

Notez également que cette fonctionnalité ne fonctionne que pour l'interopérabilité avec Java ; étant donné que Kotlin a des types de fonction appropriés, la conversion automatique des fonctions en implémentations d'interfaces Kotlin est inutile et n'est donc pas prise en charge.

Pour mettre en œuvre une interface comme vous le souhaitez, vous devez utiliser l'expression d'objet. Regardez aussi fonctions d'ordre élevé - Je pense que vous en avez besoin pour votre solution.

internal interface SomeStupidInterface {
    interface ChangeListener {
        fun notifyChanged(element: Any)
    }

    companion object FACTORY {
        fun createNew(): ChangeListener {
            val time = System.currentTimeMillis()
            return object : ChangeListener {
                override fun notifyChanged(element: Any) {
                    println("J2KO" + "time " + time)
                }
            }
        }

        fun createTheSame(): ChangeListener {
            return object : ChangeListener {
                override fun notifyChanged(element: Any) {
                    println("J2KO" + "time " + System.currentTimeMillis())
                }
            }
        }
    }

    fun notifyChanged()
}

Dans IntelliJ IDEA, je ne peux pas non plus compiler votre code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X