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:
- Kotlin: Cómo trabajar con Casts de listas: Desmarcado Cast: kotlin.collections.List <Kotlin.Any?> A kotlin.colletions.List <Waypoint>
- Dagger 2 Named no se puede proporcionar sin un método @Provides
- Kotlin y ArgumentCaptor - IllegalStateException
- Gradle dependencia entre tareas
- Corotinas de Kotlin de la manera correcta en Android
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 🙂
- Las propiedades y los valores perezoso de Kotlin se restablecen: un delegado perezoso reajustable
- RequiereApi vs TargetApi anotaciones android
- Las colecciones de Kotlin lanzan ClassNotFoundException en Android
- Android Kotlin: Error Referencia sin resolver: DaggerAppComponent
- ¿Es Kotlin 100% compatible con ART en Android?
- ¿Cómo hacer referencia a otras opiniones en Anko DSL?
- Kotlin - equivalencia a la combinación de Swift de "if let + cast"
- Error de procesamiento en Android Studio 3.0 Layout Editor
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.
- Configurar el tono de llamada personalizado de contacto, ¿cómo?
- Cómo cambiar la contraseña usando la contraseña antigua en el análisis de android