열거형의 준말이다.
상태를 구분하기 위한 객체들을 이름을 붙여 여러개 생성해두고 그중 하나의 상태를 선택하여 나타내기 위한 클래스이다.
enum class Color(val number: Int) {
RED(1), BLUE(2), GREEN(3);
fun isRed() = this == Color.RED
}
fun main() {
var color = Color.BLUE
println(color)
println(color.number)
println(color.isRed())
}
이때 비교할 대상은 State 객체 자기 자신이므로 this로 기재한다.
Enum은 Red라는 하위 객체를 Singleton처럼 1개만 생성할 수 있고 복수의 객체는 생성할 수는 없다. 같은 맥락으로 enum 클래스의 속성을 변경할 수 없다.
val aa = Colorr.BLUE
val bb = Colorr.BLUE
aa.r = 120
println(aa.r)
println(bb.r)
// 120
// 120
aa와 bb는 같은 객체이기 때문에 값이 같이 바뀌어 버린다.. 이러한 단점을 극복할 수 있는게 sealed class이다. sealed class는 1개 이상의 객체를 생성할 수 있다.
Super class를 상속받는 Child 클래스의 종류 제한하는 특성을 갖고 있는 클래스다.
클래스 앞에 sealed 키워드를 붙이면 이 클래스는 abstract 클래스가 된다. 그리고 하위 클래스가 이 클래스를 상속하도록 선언하면 된다.
sealed class Color {
data class Red(val r: Int, val g: Int, val b: Int) : Color()
data class Orange(val r: Int, val g: Int, val b: Int) : Color()
data class Yellow(val r: Int, val g: Int, val b: Int) : Color()
data class Green(val r: Int, val g: Int, val b: Int) : Color()
data class Blue(val r: Int, val g: Int, val b: Int) : Color()
data class Indigo(val r: Int, val g: Int, val b: Int) : Color()
data class Violet(val r: Int, val g: Int, val b: Int) : Color()
}
sealed class Color
data class Red(val r: Int, val g: Int, val b: Int) : Color()
data class Orange(val r: Int, val g: Int, val b: Int) : Color()
data class Yellow(val r: Int, val g: Int, val b: Int) : Color()
data class Green(val r: Int, val g: Int, val b: Int) : Color()
data class Blue(val r: Int, val g: Int, val b: Int) : Color()
data class Indigo(val r: Int, val g: Int, val b: Int) : Color()
data class Violet(val r: Int, val g: Int, val b: Int) : Color()
둘 다 같은 의미이다.
괄호로 묶지 않고 작성했을 땐 이렇게 사용한다.
sealed class Result
open class Success(val message: String) : Result()
class Error(val code: Int, val message: String) : Result()
class Status: Result()
class Inside: Success("Status")
fun eval(result: Result): String = when(result) {
is Status -> "in progress"
is Success -> result.message
is Error -> result.message
}
fun main() {
val result = Success("Good!")
val msg = eval(result)
println(msg)
}
하위 클래스를 제한해서 얻는 이점 중에 하나는 when을 사용할 때다. when은 모든 케이스에 대해서 처리가 되어야 하기 때문에 else 구문이 꼭 들어가야 한다. 하지만 Sealed class를 사용하면 컴파일 시점에 하위 클래스들이 정해져있기 때문에, 모든 하위 클래스에 대한 케이스를 구현하면 else 구문을 추가하지 않을 수 있다.
String으로 타입을 체크하여 font를 결정하는 코드입니다. 이 코드의 when에는 else가 필요하다. 없으면 error가 난다.
val color = "red"
val font = when (color) {
"red" -> {
"Noto Sans"
}
"green" -> {
"Open Sanse"
}
else -> {
"Arial"
}
}
만약 blue에 대한 것을 추가하여 확장하고 싶다면 when에 다음과 같이 코드를 추가해야 한다. 하지만 실수로 blue에 대한 코드를 추가하지 않았다고 했을때 컴파일이 잘 될까? 잘 된다! 그 이유는 else 구문이 있기 때문이다. 하지만 이상한 결과를 출력하게 되겠죠..
이제 이 상황을 sealed class 로 똑같이 구현을 해보면 else 가 없어도 error가 나지는 않지만, 분명 Blue 클래스를 갖고 있는것을 알기 때문에 when 안에 Blue에 대한 코드를 추가하지 않으면 컴파일 에러가 발생한다.
sealed class Color {
object Red: Color()
object Green: Color()
object Blue: Color()
}
val color : Color = Color.Red
val font = when (color) {
is Color.Red -> {
"Noto Sans"
}
is Color.Green -> {
"Open Sans"
}
// compile error!
}
정리하면, sealed class는 컴파일 시점에 존재할 수 있는 클래스 타입이 정해져 있기 때문에 when을 사용할 때 else를 사용하지 않아도 된다. else를 사용하지 않았기 때문에 기능을 확장할 때 위의 예제와 같이 실수로 코드를 추가하지 않는 일이 발생하지 않게 된다.
코틀린에서 제공하는 Enum도 하위 클래스의 타입들이 정해져있다는 점에서 Sealed class와 비슷하다. 가장 큰 차이점은 Enum은 Single instance만 만들 수 있는 반면에 Sealed class는 객체를 여러개 생성할 수 있다는 것이다.
이를 활용하여, 아래와 같은 동작이 가능하다.
fun isCoolToneRed(color: Color.Red) = color.b >= 20
fun main() {
val redA = Color.Red(255, 0, 20)
val redB = Color.Red(255, 0, 0)
println(isCoolToneRed(redA)) // true
println(isCoolToneRed(redB)) // false
}
sealed 클래스의 서브 클래스 각각에 대해 여러 개의 인스턴스 생성 가능하기 때문에 상태값을 유동적으로 변경할 수 있다.