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.

  • ¿Kotlin Android Extensions pone en caché las propiedades sintéticas o cada vez que llama a findViewById ()?
  • Kotlin.TypeCastException: null no se puede convertir en tipo no null com.midsizemango.databasekotlin.Note
  • Advertencia de tiempo de ejecución de Kotlin desactualizada (plugin Kotlin 1.1.2-release-Studio2.3-3)
  • ¿Hay alguna manera de reutilizar una instancia de Job?
  • ¿Hay una sobrecarga para escribir una biblioteca en Kotlin para Android?
  • Manejador de errores por defecto de RxJava
  • Kotlin: ¿Qué significa "return @"?
  • ¿Cómo acceder a "Activity.this" en Kotlin?
  • Sala de Android: Cada variable de enlace en la consulta debe tener un método de coincidencia
  • Suprimir "Identificador no permitido en Android"
  • No se puede burlar la clase final de Kotlin usando Mockito 2
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.