Primary/Kotlin

[Kotlin] Nested Class, Inner Class

해스끼 2022. 5. 19. 15:16

Kotlin에서도 클래스 안에 다른 클래스를 정의할 수 있다. 특정 외부 클래스에 종속된 클래스를 외부로부터 숨기고자 할 때 유용하다. 그러나 Kotlin의 내부 클래스는 기본적으로 외부 클래스의 멤버에 접근할 수 없다. 예시를 하나 들어 보겠다.

 

``View``를 상속하여 커스텀 뷰를 만들려 한다. 이때 ``View``를 직렬화(serialize) 가능하게 만든다고 생각해 보자. 뷰 자체를 직렬화하기는 쉽지 않지만, 대신 뷰가 가지고 있는 데이터를 직렬화하면 된다. 뷰의 데이터를 담는 일반적인 객체를 정의하기 위해 ``Serializable``을 구현하는 ``State`` 인터페이스를 정의하자. 

interface State: Serializable

interface View {
    fun getState(): State
    fun restoreState(state: State) {}
}

이제 나만의 `Button` 클래스를 작성할 수 있다. 먼저 자바 코드를 보자.

public class Button implements View {
    @Override
    public State getState() {
        return new ButtonState();
    }
    
    @Override
    public void restoreState(State state) { /*...*/ }
    
    public class ButtonState implements State { /*...*/ }
}

그런데 이 코드는 잘못되었다. 실제로 이 코드를 실행하면 ``NotSerializableException``이 발생한다. 아니 분명히 ``ButtonState``를 구현했는데? 왜지?

 

자바에서는 클래스 안에 정의한 클래스는 기본적으로 ``inner class``이기 때문이다. 위에서 정의한 ``ButtonState``는 ``Button`` 클래스의 변수와 함수에 접근할 수 있다. ``ButtonState`` 그 자체는 직렬화 가능하지만, 직렬화 불가능한 ``Button``을 참조하고 있기 때문에 결과적으로 ``ButtonState``는 직렬화할 수 없다. 만약 ``ButtonState``가 ``Button`` 밖에 정의됐더라면 문제가 없었을 것이다.

 

``ButtonState``를 ``static class``로 정의하면 이 문제를 해결할 수 있다. ``static``으로 정의된 중첩(nested) 클래스는 외부 클래스를 참조하지 않는다.

 

방금 말한 static nested class가 정확히 Kotlin의 기본값이다.

class Button : View {
    override fun getState(): State = ButtonState()
    
    override fun restoreState(state: State) { /*...*/ }
    
    class ButtonState: State { /*...*/ }
}

Kotlin의 내부 클래스는 기본적으로 외부 클래스를 참조할 수 없다. 외부 클래스를 참조할 수 있게 하고 싶다면 ``ButtonState``를 ``inner class``로 선언해야 한다. 

 

정리하면 다음과 같다.

  • 외부 클래스에 접근할 수 없는 nested class: ``static class A``(Java), ``class A``(Kotlin)
  • 외부 클래스에 접근할 수 있는 inner class: ``class A``(Java), ``inner class A``(Kotlin)