Post

(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)
    }

sealed interface도 있다. 단순히 묶어주는 기능이 필요한거라면 interface가 더 적합해보인다.

https://kotlinlang.org/docs/sealed-classes.html 참고

This post is licensed under CC BY 4.0 by the author.