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:
- Propiedad predeterminada perezosa de Kotlin
- Anko: alcance interno de la aplicación, confundir la resolución de la propiedad
- Procesamiento de anotaciones con kapt Android Studio
- Generar errores con el nuevo kotlin 1.1, kapt no puede analizar parámetros de conexión de datos
- Realm, inicializar una matriz de cadena vacía devuelve "Tipo no soportado java.lang.String " en la compilación
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 🙂
- "No se puede encontrar la clase referenciada" con Proguard y Kotlin
- Kotlin - La forma más idiomática de convertir una lista a una MutableList
- Retrofit2 y kotlin
- Jackson no puede cargar tipos JDK7 en Android
- Kotlin palabra clave en línea que causa IntelliJ IDEA Reporte de cobertura 0%
- Referencia no resuelta para Dagger 2 en Kotlin
- ¿Es posible reutilizar un diseño en Kotlin Anko
- Prueba de unidad en la función de extensión de Kotlin en las clases de SDK de Android
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