Kotlin devuelve el mismo objeto del método Factory

Estoy jugando con Kotlin y encontré un comportamiento interesante. Así que digamos que quiero tener algún tipo de fábrica:

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

Donde ChangeListener define en el archivo java:

 interface ChangeListener { void notifyChange(Object element); } 

Y luego intento usarlo de Java así:

 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)); 

Los resultados son:

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

Puedo entender por qué createNew devuelve nuevos objetos debido al cierre. ¿Pero por qué estoy recibiendo la misma instancia del método createTheSame ?

PS Sé que el código anterior no es idiomático 🙂

Primera nota: el código de ejemplo no funciona como está: la interfaz tiene que estar escrita en Java para estar disponible para su uso con los constructores de SAM.

En cuanto a la pregunta real, ya has tocado por qué este comportamiento está sucediendo. Lambdas (en este caso, los constructores SAM) se compilan en clases anónimas (a menos que estén en línea). Si captan cualquier variable externa, entonces para cada invocación, se creará una nueva instancia de la clase anónima. De lo contrario, ya que no tienen que tener ningún estado, sólo una sola instancia apoyará cada invocación de la lambda. Supongo que esto es por razones de rendimiento, si nada más. (Crédito al libro de Kotlin en Acción para la información en este párrafo.)

Si desea devolver una nueva instancia cada vez sin capturar ninguna variable, puede utilizar la notación de object completo:

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

Llamar a la función anterior varias veces devolverá diferentes instancias para cada llamada. Curiosamente, IntelliJ sugerirá convertir esta sintaxis de conversión de SAM original en su lugar:

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

Que, como ya has descubierto, devuelve la misma instancia cada vez.

Supongo que esta conversión se ofrece porque comparar si estos casos apátridas son iguales es un caso muy importante. Si necesita ser capaz de hacer la comparación entre las instancias que se devuelven, es probable que sea mejor con la notación de object completo. A continuación, incluso puede agregar algún estado adicional a cada oyente, en forma de un id por ejemplo.

Esto tiene que ver con el rendimiento. Crear menos objetos obviamente es mejor para el rendimiento, por lo que es lo que Kotlin intenta hacer.

Para cada lambda, Kotlin genera una clase que implementa la interfaz adecuada. Por ejemplo, el siguiente código Kotlin:

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

Corresponde con algo como:

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

Puede ver aquí que siempre se devuelve la misma instancia.


Si hace referencia a una variable que está fuera del ámbito de lamdba sin embargo, esto no funcionará: no hay forma de que la instancia singleton acceda a esa variable.

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

En su lugar, para cada invocación de create , una nueva instancia de la clase necesita ser instanciada que tiene acceso a la variable de text :

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

Es por eso que sus instancias a y b son las mismas, pero c y d no lo son.

Parece que intenta utilizar la conversión de SAM con la interfaz de Kotlin.

Tenga en cuenta que las conversiones SAM sólo funcionan para las interfaces, no para las clases abstractas, incluso si éstas también tienen un solo método abstracto.

También tenga en cuenta que esta función sólo funciona para Java interop; Ya que Kotlin tiene tipos de funciones adecuadas, la conversión automática de funciones en implementaciones de interfaces Kotlin es innecesaria y, por lo tanto, no es compatible.

Para implementar la interfaz como desee, necesita utilizar la expresión de objeto. También mirar las funciones de alto orden – creo que los necesita para su solución.

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

También en IntelliJ IDEA No puedo compilar su código.

  • Dagger 2 error: dependency "no se puede proporcionar sin un constructor @Inject"
  • ClassNotFoundException en el sabor personalizado con kotlin
  • Cómo resolver: "error: no se puede encontrar la clase de símbolo ..." después de convertir la clase Java a Kotlin?
  • Horizontal LinearLayout en Anko
  • Cómo setOnEditorActionListener con Kotlin
  • Kotlin - Agregar artículos a ExpandableListView
  • Android Studio 3 canary y Kotlin
  • Gson no puede analizar una cadena de datos de formato json en Kotlin
  • Variable 'runnable' debe ser inicializado
  • ¿Cómo interactúa Kotlin con Java y JavaScript?
  • Archivo Kotlin vs Clase. ¿Cual es la diferencia?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.