Al igual que en Java, en Kotlin puedes declarar una clase en otra clase. Hacerlo puede ser útil para encapsular una clase auxiliar o colocar el código más cerca de donde se usa. La diferencia es que las clases anidadas de Kotlin no tienen acceso a la instancia de la clase externa, a menos que lo solicite específicamente. Veamos un ejemplo que muestra por qué esto es importante.
Imagine que desea definir un elemento View, cuyo estado se puede serializar. Puede que no sea fácil serializar una vista, pero puede copiar todos los datos necesarios en otra clase auxiliar. Declaras la interfaz State que implementa Serializable. La interfaz View declara los métodos getCurrentState y restoreState que se pueden usar para guardar el estado de una vista.
interface State: Serializable
interface View {
fun getCurrentState(): State
fun restoreState(state: State) {}
}
Es útil definir una clase que guarde el estado de un botón en la clase Botón. Veamos cómo se puede hacer en Java (el código Kotlin similar se mostrará en un momento).
/* Java */
public class Button implements View {
@Override
public State getCurrentState() {
return new ButtonState();
}
@Override
public void restoreState(State state) { /*...*/ }
public class ButtonState implements State { /*...*/ }
}
La clase ButtonState que implementa la interfaz State y contiene información específica para Button. En el método getCurrentState, crea una nueva instancia de esta clase. En un caso real, inicializaría ButtonState con todos los datos necesarios.
¿Qué tiene de malo este código? ¿Por qué se obtiene un java.io.NotSerializableException? Eso puede parecer extraño al principio: ButtonState guarda una referencia a Button que no es Serializable.
Todo queda claro cuando recuerdas que en Java, cuando declaras una clase en otra clase, se convierte en una clase interna por defecto. La clase ButtonState del ejemplo almacena implícitamente una referencia a su clase Button externa. Eso explica por qué ButtonState no se puede serializar: Button no se puede serializar y la referencia a él interrumpe la serialización de ButtonState.
Para solucionar este problema, debe declarar la clase ButtonState como estática. Declarar una clase anidada como estática elimina la referencia implícita de esa clase a su clase adjunta. En Kotlin, el comportamiento predeterminado de las clases internas es lo opuesto a lo que acabamos de describir.
class Button : View {
override fun getCurrentState(): State = ButtonState()
override fun restoreState(state: State) { /*...*/ }
class ButtonState : State { /*...*/ }
}
Una clase anidada en Kotlin sin modificadores explícitos es lo mismo que una clase anidada estática en Java. Para convertirlo en una clase interna para que contenga una referencia a una clase externa se puede usar el modificador inner.
La sintaxis para hacer referencia a una instancia de una clase externa en Kotlin también difiere de Java. Se escribe this@Outer para acceder a la clase Outer desde la clase Inner:
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}