본문 바로가기

Kotlin

Sealed Class, Enum 제대로 알기

개발을 하다보면 상수 타입에 대한 구분을 두는 것이 필요할 때가 있습니다.

정적 변수 선언을 통해 이를 해결할 수 있으나, 이 마저도 너무 많아지면 메모리에 대한

문제가 생길 수 있기에 좋은 방법은 아닙니다. (물론 소량이라면 괜찮습니다. 여전히 좋은 방식입니다.)

 


 

코틀린은 EnumSealed Class / Interface 를 제공합니다. 단순히 정적 변수를 선언하는 것에 따라 발생할 수 있는 부작용들을 사전에 차단할 수 있는 방안들입니다.

그 중 Sealed Class 는 Enum 과는 엄연히 다른 이고, 그다지 관계가 없어 보이기까지 할 수 있지만, 상황에 따라 둘을 같은 용도로 사용할 수 있기도 합니다.

 

직접 개발한 <에이펙싱> 에서, 전적을 살펴볼 수 있는 화면에서는 한 개의 RecyclerView 에 여러 개의 ViewHolder 를 붙였습니다. ViewHolder 는 각 타입에 따라 적절한 View 에 Binding 해줍니다. 이를 위해 Sealed Class 를 사용하였습니다. Enum 을 사용하여도 가능했을 것입니다.

 

한 화면 맞습니다. 배경색이 이상한 건 두 장의 스크린 샷을 합쳤기 때문입니다.

 

위 이미지는 StatisticFragment 에 RecyclerView 와 FloatingActionButton 을 하나씩 구현했습니다.

하나의 RecyclerViewAdapter 에 여러 ViewHolder 를 둘 수 있는 것은, 해당 Adapter 가 받는 타입이

Sealed Class 이고, 해당 Sealed Class 내부에 여러 서브 클래스를 선언해 두었기 때문에 가능합니다.

 


 

Sealed Class

 

Sealed Class 는 그 클래스를 상속받는 클래스들에 대하여 종류를 제한합니다. 특정 클래스를 상속받는 클래스는 여러 파일에 산개해 있을 수 있고, 또 그 수가 굉장히 많을 수 있습니다. 컴파일러는 그 수와 경로를 모두 알고 있지 못하기 때문에 특정 종류나 타입에 대해 제한할 수 없습니다. 

기준이 되는 수퍼 클래스를 Sealed Class 로 만들고, 같은 종류의 클래스들이 수퍼 클래스를 상속 받게 하면

컴파일러는 해당 수퍼 클래스의 서브 클래스들을 컴파일 타임에 파악할 수 있게 됩니다.

 

선언법은 다음과 같습니다. sealed class 는 abstract 로 선언되기 때문에 기본적으로 인스턴스화가 불가능합니다. 또한 sealed class 의 접근제어자는 protected 이기 때문에 다른 패키지에서는 참조할 수 없습니다.

 

sealed class GameMode {
	class Arena: GameMode
	class BattleRoyal: GameMode
	class Control: GameMode
}

 

생성자 파라미터를 추가할 수 있습니다.

 

sealed class GameMode(val isRankExist: Boolean) {
	class Arena: GameMode(true)
	class BattleRoyale: GameMode(true)
	class Control: GameMode(false)
}

 

당연히, 각각의 서브 클래스에 생성자 파라미터를 추가할 수도 있습니다.

 

Sealed Class 의 장점은 when 사용시 else 에 대한 처리를 진행하지 않아도 된다는 것입니다.

 

GameMode 가 sealed class 가 아닌 open class&nbsp; 일 때

 

GameMode 클래스를 open class 로 선언하고 hasRankMode() 에서 들어온 GameMode 의 서브 타입에 따라 다르게 값을 반환하려 할 때, else 구문에 대한 처리를 하지 않으면 컴파일러가 경고를 줍니다.

 

GameMode 를 sealed class 로 선언한 경우

 

이 경우엔 경고가 사라집니다. 

 

🤔 만약 한 클래스를 빠트리면 어떻게 되나요?

 

당연히 컴파일러가 하나 빠졌다고 알려줍니다😊

 


 

Enum

 

Enum (열거형) 클래스는 자바의 그 것과 정확히 같습니다. Enum 내에 선언된 상수들은 컴파일 타임에 메모리에 적재되어 프로그램이 끝날 때 까지 메모리에서 해제되지 않습니다. 또한 상수이기 때문에 객체도 단 하나만 존재합니다.

 

선언법은 다음과 같습니다.

 

enum class Legends {
    BLOODHOUND,
    WRAITH,
    OCTANE,
    PATHFINDER,
    LIFELINE
}

 

특정 값을 지정해 줄 수도 있습니다.

 

enum class Legends(val gender: String) {
    BLOODHOUND("None-Binary"),
    WRAITH("Female"),
    OCTANE("Male"),
    PATHFINDER("Null"),
    LIFELINE("Female")
}

 

Enum 역시 메서드를 포함할 수 있는데, Enum Class 내부에 추상 메서드를 정의하고, 각 항목마다 이를 정의해주면 됩니다.

 

enum class MyEnum {
    BLOODHOUND {
        override fun printSkill() {
            println("Scanning this area")
        }
    },
    WRAITH {
        override fun printSkill() {
            println("Void jumping")
        }
    };

    abstract fun printSkill()
}

 


 

🤔 차이점은?

 

가장 큰 차이점은 일단 메모리입니다. 

Enum 의 경우, 컴파일 타임에 상수로 생성되어 메모리에 적재된 뒤 프로그램이 종료될 때 까지 메모리에서 해제되지 않습니다. 너무 많은 양의 값을 선언해두면 메모리 릭의 위험이 있습니다.

 

그러나 Sealed Class 의 경우, 결국에는 클래스이기 때문에 참조될 때 메모리에 적재되고, 참조되지 않을 때 GC 에 의해 메모리에서 해제됩니다. (당연히 Sealed Class 안에 object 를 선언하면 이는 상수로 생성되어 컴파일 타임에 메모리에 적재됩니다.)

 

또한, Enum 에선 함수를 선언할 수 없지만 Sealed Class 에선 함수를 선언할 수 있습니다.

 

성능적 차이도 있는데, when 키워드와 함께 사용했을 때, Sealed Class 보다 Enum Class 가 성능이 좋습니다. Sealed Class 는 비교를 위해 내부적으로 instanceOf 를 사용하기 때문입니다.

 

결론

여러 클래스를 특정 타입으로 제한하고 싶은 경우, Enum Class Sealed Class 취사선택할 수 있다.

각 항목이 어떠한 값도 가지지 않고 상수 그 자체로 존재하거나, 불변의 값을 갖고 있다면 Enum Class 를.

그렇지 않은 경우에는 Sealed Class 를 사용하자!

 


종종 사용하면서도 어떤 차이가 있는지 몰랐는데, 이번 기회로 확실히 정리할 수 있었습니다.