2.2 코틀린 기초 / 선택 표현과 처리: enum과 when

코틀린의 when 은 자바의 switch를 대치하되 훨씬 더 강력하고 자주사용한다.

2.3.1 enum 클래스 정의

enum class Color{
    RED,ORANGE,YELLOW,GREEN,BLUE,
}

enum 은 단순 값만 열거하는 존재가 아니라 프로퍼티나 메서드를 정의할 수 있다.

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
}

fun main(args: Array<String>) {
    println(Color.BLUE.rgb())
}

여기서 코틀린 중에서 유일하게 ; 를 사용하는 것을 볼 수 있다.

enum 클래스 안에 메서들르 정의하는 경우
반드시 enum 상수 목록메서드 정의 사이에 세미콜론을 넣어야한다.

2.3.2 when 으로 enum 클래스 다루기

enum class Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

fun getMnemonic(color: Color) =
    when (color) {
        Color.RED -> "Richard"
        Color.ORANGE -> "Of"
        Color.YELLOW -> "York"
        Color.GREEN -> "Gave"
        Color.BLUE -> "Battle"
        Color.INDIGO -> "In"
        Color.VIOLET -> "Vain"
    }

fun main(args: Array<String>) {
    println(getMnemonic(Color.BLUE))
}

자바와 다르게 break 를 안 넣어도 된다.

한 분기 안에 여러 값을 매치 패턴으로 사용할 수 있다. 이런 경우 , 를 이용해주면 된다.

enum class Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

fun getWarmth(color: Color) = when(color) {
    Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
    Color.GREEN -> "neutral"
    Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}

fun main(args: Array<String>) {
    p

2.3.3 when과 임의의 객체를 함께 사용

상수만 사용할 수 있는 자바의 switch와 달리 코틀린의 when은 임의의 객체 모두를 허용한다.

fun mix(c1: Color, c2: Color) =
        when (setOf(c1, c2)) {
            setOf(RED, YELLOW) -> ORANGE
            setOf(YELLOW, BLUE) -> GREEN
            setOf(BLUE, VIOLET) -> INDIGO
            else -> throw Exception("Dirty color")
        }

fun main(args: Array<String>) {
    println(mix(BLUE, YELLOW))
}

2.3.4 인자 없는 when 사용

인자가 없으면 코드 읽기가 어려워지지만 성능을 더 향상시킨다.

fun mixOptimized(c1: Color, c2: Color) =
    when {
        (c1 == RED && c2 == YELLOW) ||
        (c1 == YELLOW && c2 == RED) ->
            ORANGE

        (c1 == YELLOW && c2 == BLUE) ||
        (c1 == BLUE && c2 == YELLOW) ->
            GREEN

        (c1 == BLUE && c2 == VIOLET) ||
        (c1 == VIOLET && c2 == BLUE) ->
            INDIGO

        else -> throw Exception("Dirty color")
    }

fun main(args: Array<String>) {
    println(mixOptimized(BLUE, YELLOW))
}

위의 코드에 when 에 아무 인자도 없다
=> Boolean 값으로 판단.

2.3.5 스마트 캐스트: 타입 검사와 타입 캐스트를 조합

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int {
    if (e is Num) {
        val n = e as Num       //1)
        return n.value
    }
    if (e is Sum) {
        return eval(e.right) + eval(e.left) //2)
    }
    throw IllegalArgumentException("Unknown expression")
}

fun main(args: Array<String>) {
    println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}

Sum 의 경우 좌우 인자에 대해서 left 와 right property로 저장한다.

Expr 인터페이스에는 두 가지 구현 클래스가 존재
따라서 식을 평가하려면 두 가지 경우를 고려해야한다.

  • 어떤 식이 수라면 그 값을 반환한다
  • 어떤 식이 합계라면 좌항과 우항의 값을 계산한 다음에 그 두 값을 합한 값을 반환한다.

1) 에서 Num으로 타입을 변환하는데 이는 불필요한 중복이다.
2)변수 e에 대해 스마트 캐스트를 사용한다.

  • 코틀린에서는 is 를 이용하여 변수 타입을 검사한다.
  • 원하는 타입으로 명시적으로 타입 캐스팅하려면 as 키워들르 사용한다.

스마트 캐스트는 값이 바뀔 수 없는 경우에만 작동!! => 해당 property에 대한 접근이 항상 같은 값을 내놓는다고 확인을 해야한다.

2.3.6 리팩토링: if를 when 으로 변경

fun eval(e: Expr): Int =
    if (e is Num) {
        e.value
    } else if (e is Sum) {
        eval(e.right) + eval(e.left)
    } else {
        throw IllegalArgumentException("Unknown expression")
    }

fun main(args: Array<String>) {
    println(eval(Sum(Num(1), Num(2))))
}

위의 코드를 when 을 이용해서 다듬어 보자.

fun eval(e: Expr): Int =
    when (e) {
        is Num -> //1)
            e.value  //2)
        is Sum -> //1)
            eval(e.right) + eval(e.left)  //2)
        else ->
            throw IllegalArgumentException("Unknown expression")
    }

fun main(args: Array<String>) {
    println(eval(Sum(Num(1), Num(2))))
}

1) 인자 타입을 검사하는 when 분기들
2) 이부분에 스마트 캐스트가 쓰였다.

타입을 검사하고나면 스마트 캐스트가 이뤄진다
-> Num 이나 Sum의 멤버에 접근할 때 변수를 강제로 캐스팅할 필요가 없다.

2.3.7 if와 when 의 분기에서 블록 사용

fun evalWithLogging(e: Expr): Int =
    when (e) {
        is Num -> {
            println("num: ${e.value}")
            e.value    //1)
        }
        is Sum -> {
            val left = evalWithLogging(e.left)
            val right = evalWithLogging(e.right)
            println("sum: $left + $right")
            left + right   //2)
        }
        else -> throw IllegalArgumentException("Unknown expression")
    }

fun main(args: Array<String>) {
    println(evalWithLogging(Sum(Sum(Num(1), Num(2)), Num(4))))
}

1) 이 식이 블록의 마지막 식이므로 e의 타입이 Num 이면 e.value를 반환
2) e의 타입이 Sum 이면 이 식의 값이 반환된다.