(Kotlin) enum / when / sealed
enum
자바처럼 enum
클래스 안에 프로퍼티나 메소드를 정의할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
enum class Color(val r: Int, val g: Int, val b: Int) {
RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0),
BLUE(0, 0, 255), INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
>>> Color.RED.ordinal // index
0
>>> Color.RED.r
255
enum은 class 상속은 불가 하고 interface 구현은 가능한데, interface가 필드 가지도록 해서 아래와 같은 코드를 인터페이스에 일원화 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
interface AddrInterface {
val real: String
val test: String
fun getAddr(chain: Chain): String {
return when (chain) {
Chain.REAL -> real
Chain.TEST -> test
}
}
}
참고로 이런 경우 Address라는 {real, test}를 가지고 있는 타입을 따로 정의하고 메서드를 Address에 위치시키는 것도 방법이다.
when
switch
대신 kt when
을 사용하며 더 강력하다. when
도 식이다.
1
2
3
4
5
fun getWarmth(color: Color) = when (color) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
분기 조건에 상수만 사용할 수 있는 자바의 switch
와 달리 when
은 분기 조건에 임의 객체를 사용할 수 있다.
1
2
3
4
5
6
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) { // 집합을 인자로 받는다.
setOf(RED, YELLOW) -> ORANGE
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
setOf()
가 반복적으로 호출되는게 마음에 들지 않는다면, 다음과 같이 인자 없는 when
을 사용해 가독성에 손해를 보는 대신 성능을 올릴 수 있다.
인자 없는 when
을 사용하려면 각 분기 조건은 반드시 불리언 결과를 계산하는 식이어야 한다.
1
2
3
4
5
6
7
8
9
fun mixOpt(c1: Color, c2: Color): String {
when {
(c1 == RED && c2 == YELLOW) ||
(c1 == YELLOW && c2 == RED) -> return "ORANGE"
(c1 == BLUE && c2 == VIOLET) ||
(c1 == VIOLET && c2 == BLUE) -> return "INDIGO"
else -> throw Exception("Dirty color")
}
}
Sealed classes and interfaces : sealed 안의 모든 경우의 수에 대해 분기 처리하도록 강제 (실수 방지)
일반적인 경우 when
에는 반드시 else
(디폴트 분기)를 적어주어야 한다.
Expr
클래스 계층에 새로운 하위 클래스를 추가했으나 when
에는 추가하는 것을 깜빡한다면, 디폴트 분기를 타면서 프로그램이 예상대로 동작하는 것 처럼 보여, 시스템에 문제가 있다는 사실을 알아채기 어려울 수 있다.
1
2
3
4
5
6
7
8
9
10
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else -> throw IllegalArgumentException("Unknown expresssion")
}
sealed class (중첩 클래스)
이런 경우 sealed
와 중첩 클래스를 사용하면 디폴트 분기를 사용하지 않고 모든 경우의 수를 처리하도록 강제할 수 있어 더 확실하게 검사할 수 있다.
sealed
class는 자동으로 kt open
이다. 상속을 전제로 만들어진 기능이라서.
1
2
3
4
5
6
7
8
9
10
11
12
13
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
class Minus(val left: Expr, val right: Expr) : Expr() // 꼭 중첩 클래스로 쓸 필요는 없다.
fun eval(e: Expr): Int =
when (e) {
is Expr.Num -> e.value
is Expr.Sum -> eval(e.right) + eval(e.left)
is Minus -> eval(e.left) - eval(e.right)
}