2. [3] 선택 표현과 처리: enum과 when

(1) enum class

enum class Color {
 RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
  • Kotlin에서 enum은 소프트 키워드라 부르는 존재이다. 이는 Java에서 보다 더 많은 키워드를 써야 한다.
  • 자바와 달리 값을 열거만 하는 것이 아닌, enum class안에 프로퍼티나 메서드를 정의할 수 있다.
enum class Color(val r: Int, val g: Int, val b: Int) { // 상수의 프로퍼티 정의
 RED(255, 0, 0), // 각 상수를 생성할 때 그에 대한 프로퍼티 값을 지정한다.
 ORAGNE(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 // enum class 안에서 aptjem
}

(2) when으로 enum class 다루기

fun getMnemonic(color: Color) =      // 함수의 반환 값으로 when 식을 직접 사용
   when (color) {
       Color.RED, Color.ORANGE, Color.YELLOW -> "Warm"
    Color.GREEN -> "Netural"
    Color.BLUE, Color.INDIGO, Color.VIOLET -> "Cold"
  }
  • Java와 달리 각 분기에 끝에 Break를 넣지 않아도 오류가 생기지 않음
  • 한 분기 안에서 여러 적으로 매치 패턴을 사용할 때는 , 를 사용하여 분리
  • 상수 값을 임포트 하면 해당 코드를 더 간단하게 만들 수 있음.
import ch02.colors.Color
import ch02.colors.Color.*
fun getMnemonic(color: Color) =
   when (color) {
       RED, ORANGE, YELLOW -> "Warm"
    GREEN -> "Netural"
    BLUE, INDIGO, VIOLET -> "Cold"
  }

(3) 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") // 매치되는 분기 조건이 존재하지 않을 시
}
println(mix(BLUE, YELLOW))  // GREEN
  • Java의 switch와 달리, Kotlin의 when은 임의의 객체를 허용한다.
  • 인자로 전달받은 여러 객체를 그 객체들을 포함하는 집합인 Set 객체로 만드는 setOf 함수가 존재함. 이는 순서는 상관없음

(4) 인자 없는 when 사용

fun mix(c1: Color, c2: Color) =
   when { // 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("더러운 색깔")
  }
  • 임의의 객체와 함께 when을 사용하는 경우, 호출될 때마다 주어진 두 색이 when의 분기 조건에 있는 다른 두 색과 같은지 비교하기 위해서 여러 set 인스턴스를 생성하기 때문에 비효율적임
  • when에 아무 인자가 없다면 각 분기의 조건이 Boolean 결과를 계산하는 식이어야 함.

(5) 스마트 캐스트

interface Expr
class Num(val value: Int): Expr // value라는 프로퍼티만 존재하는 클래스. Expr 인터페이스 구현
class Sum(val left: Expr, val right.Expr): Expr
fun eval(e: Expr): Int {
 if(e is Num) {
   val n = e as Num
   return n.value
}
 if(e is Sum) {
   return eval(e.right) + eval(e.left) // 변수 e에 대한 스마트 캐스트
}
 throw IllegalArgumentException("Unknown Expression")
}
println(eval(Sum(Sum(Num(1), Num(2)), Num(4)))) // 7
  • Kotlin에서 is를 이용하여 변수 타입을 검사하고 나면 컴파일러가 원하는 변수 타입으로 캐스팅 해준다.
  • 클래스의 프로퍼티에서 스마트캐스트를 사용한다면 그 프로퍼티는 반드시 val 이어야 하며, 커스텀 접근자를 사용할 수 없다.
  • 원하는 타입으로 명시적으로 타입 캐스팅을 해주기 위해서는 as 키워드를 사용한다.

(6) if를 when으로 변경 [Refactoring]

fun eval(e: Expt): Int =
when(e) {
is Num -> e.value // 인자 타입을 검사한 후, 스마트 캐스팅이 사용됨.
   is Sum -> eval(e.right) + eval(e.left)
   else -> throw IllegalArgumentException("Unknown Expression")
}
  • when 식은 동등성 검사가 아닌 받은 값의 타입을 검사하는 역할로도 사용할 수 있다.
  • 이는 타입을 검사하고 나면 바로 스마트 캐스팅이 이뤄지며, 따라서 Num이나 Sum의 멤버에 접근할 때 캐스팅이 필요없다.

(7) if와 when의 분기에서 블록 사용

fun evalWithLogging(e: Expr): Int {
   return when (e) {
       is Num -> {
           println("num: ${e.value}")
           e.value // e의 타입이 Num인 경우에 e.value가 반환된다.
      }
       is Sum -> {
           val left = evalWithLogging(e.left)
           val right = evalWithLogging(e.right)
           println("sum: $left + $right")
           left + right // e의 타입이 Sum인 경우에 해당 식의 값이 반환된다.
      }
       else -> throw IllegalArgumentException("Unknown Expression")
  }
}
  • eval 함수에 로그를 추가시키고 싶다면 각 분기를 블록으로 만들고, 블록의 맨 마지막에 분기의 결과 값을 위치시키면 된다.