[Kotlin IN ACTION] 02장 코틀린 기초2
2.3 선택 표현과 처리: enum과 when
when은 자바의 switch를대치하되 훨씬 더 강력하며, 앞으로 더 자주 사용할 프로그래밍 요소라고 생각할 수 있다.
when에 대해 설명하는 과정에서 코틀린에서 enum을 선언하는 방법과 스마트 캐스트에 대해서도 살펴본다.
2.3.1 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
}
println(Color.BLUE.rgb()) //255
자바와 마찬가지로 enum은 단순히 값만 열거하는 존재가 아니다.
enum 클래스 안에도 프러퍼티나 메소드를 정의할 수 있다.
enum 클래스 안에 메소드를 정의하는 경우 반드시 enum 상수 목록과 메소드 정의 사이에 세미콜론을 넣어야 한다.
2.3.2 when으로 enum 클래스 다루기
자바의 switch와 달리 when 절에서는 각 분기의 끝에 break를 넣지 않아도 된다.
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"
}
한 when 분기 안에 여러 값을 사용할 수 있다.
fun getWarmth(color: Color) =
when (color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
enum 상수 값을 임포트해서 enum 클래스 수식자 없이 enum 사용할 수 있다.
fun getWarmth(color: Color) =
when (color) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
2.3.3 when과 임의의 객체를 함께 사용
코틀린에서 when은 자바의 switch보다 훨씬 더 강력하다.
분기 조건에 상수만을 사용할 수 있는 자바 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")
}
2.3.4 인자 없는 when 사용
이전의 함수는 비교대상을 Set 인스턴스를 생성한다. 보통은 이런 비효율성이 크게 문제가 되지 않는다. 하지만 이 함수가 아주 자주 호출된다면 불필요한 가비지 객체가 늘어나는 것을 방지하기 위해 함수를 고쳐 쓰는 편이 낫다.
fun mixOptimized(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("Dirty color")
}
추가 객체를 만들지 않는다는 장점이 있지만 가독성은 더 떨어진다.
2.3.5 스마트 캐스트: 타입 검사와 타입 캐스트를 조합
코틀린에서는 is를 사용해 변수 타입을 검사한다. is 검사는 자바의 instanceof와 비슷하다.
하지만 자바에서 어떤 변수의 타입을 instanceof로 확인한 다음에 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅해야 한다.
코틀린에서는 프로그래머 대신 컴파일러가 캐스팅을 해준다. 이를 스마트캐스트라고 부른다.
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)
}
throw IllegalArgumentException("Unknown expression")
}
스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바뀔 수 없는 경우에만 작동한다.
클래스의 프로퍼티에 대해 스마트 캐스트를 사용한다면 그 프로퍼티는 반드시 val이어야한다.
해당 프로퍼티에 대한 접근이 항상 같은 값을 내놓는다고 확신할 수 없기 때문이다.
2.3.6 리팩토링: if when으로 변경
코틀린에서는 if가 값을 만들어내기 때문에 자바와 달리 3항 연산자가 따로 없다.
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")
}
when 식을 앞에서 살펴본 값 동등성 검사가 아닌 다른 기능에도 쓸 수 있다.
fun eval(e: Expr): Int =
when (e) {
is Num ->
e.value
is Sum ->
eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknown expression")
}
2.3.7 if와 when의 분기에서 블록 사용
if나 when 모두 분기에 블록을 사용할 수 있다. 그런 경우 블록의 마지막 문장이 블록 전체의 결과가 된다.
fun eval(e: Expr): Int =
when (e) {
is Num -> {
println("num: ${e.value}")
e.value
}
is Sum ->
eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknown expression")
}
2.4 대상을 이터레이션: while과 for 루프
코틀린의 while루프는 자바와 동일하다. for는 자바의 for-each 루프에 해당하는 형태만 존재한다.
2.4.1 while 루프
코틀린에는 while과 do-while 루프가 있다. 두 루프의 문법은 자바와 다르지 않다.
while(조건){
/*...*/
}
do{
/*...*/
}while(조건)
2.4.2 수에 대한 이터레이션: 범위와 수열
코틀린에는 자바의 for 루프에 해당하는 요소가 없다.
이런 루프의 가장 흔한 용례인 초깃 값, 증가 값, 최종 값을 사용한 루프를 대신하기 위해 코틀린에서는 범위를 사용한다.
for(i in 1..100){
println(i)
}
반대로 100부터 거꾸로 세되 짝수만으로 게임을 진행되도록 만들어보자.
fun main(args: Array<String>) {
for (i in 100 downTo 1 step 2) {
println(i)
}
}
끝 값을 포함하지 않는 반만 닫힌 범위에 대해 이터레이션하기를 원하면 until 함수를 사용하라.
예를들어
for(x in 0 until size){
/*...*/
}
//둘다 같다.
for(x in 0..size-1){
/*...*/
}
다음과 같이 좀 더 명확하게 개념을 표현한다.
2.4.3 맵에 대한 이터레이션
연산자를 숫자 타입의 값뿐 아니라 문자 타입의 값에도 적용할 수 있다.
for(c in 'A'..'F'){ //A부터 F까지 반복
println(c)
val binary = Integer.toBinaryString(c.Int()) //아스키 코드
binaryReps[c] = binary
}
for((letter, binary) in binaryReps){
println("$letter = $binary")
}
맵이 아닌 컬렉션에도 활용할 수 있다.
원소의 현재 인덱스를 유지하면서 컬렉션을 이터레이션할 수 있다.
인덱스를 저장하기 위한 변수를 별도로 선언하고 루프에서 매번 그 변수를 증가시킬 필요가 없다.
val list = arrayListOf("10","11","1001")
for((index, element) in list.withIndex()){
println("$index: $element")
}
//0: 10
//1: 11
//2: 1001
2.4.4 in으로 컬렉션이나 범위의 원소 검사
in 연산자를 사용해 어떤 값이 범위에 속하는지 검사할 수 있다.
반대로 !in을 사용하면 어떤 값이 범위에 속하지 않는지 검사할 수 있다.
fun isLetter(c: Char) = c in 'a'..'z' || 'A'..'z'
fun isNotDigit(c: Char) = c !in '0',,'9'
when 식에서도 사용해도 된다.
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know…"
}
2.5 코틀린의 예외 처리
코틀린의 예외처리는 자바나 다른 언어의 예외 처리와 비슷하다.
함수는 정상적으로 종료할 수 있지만 오류가 발생하면 예외를 던질 수 있다.
자바와 달리 코틀린의 throw는 식이므로 다른 식에 포함될 수 있다.
// 조건이 참이면 number의 값이 초기화되고 거짓이면 초기화되지 않고 throw를 호출한다.
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
return // 예외가 발생한 경우 catch 블록 다음의 코드는 실행되지 않는다.
}
2.5.1 try, catch, finally
자바 코드와 가장 큰 차이는 throws절이 코드에 없다는 점이다.
자바에서는 함수를 작성할 떄 함수 선언 뒤에 throws IOException을 붙여야 한다. (IOExption이 체크 예외이기 때문이다.)
코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아내도 되고 잡아내지 않아도 된다.
자바 7의 자원을 사용하는 try-with-resoucr는 어떨까? 코틀린은 그런 경우를 위한 특별한 문법을 제공하지 않는다. 하지만 라이브러리 함수로 같은 기능을 구현한다. (해당 내용은 8.2.5절에서 살펴보기로 한다.)
2.5.2 try를 식으로 사용
이전의 예제를 다시 살펴보자
// 조건이 참이면 number의 값이 초기화되고 거짓이면 초기화되지 않고 throw를 호출한다.
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
return // 예외가 발생한 경우 catch 블록 다음의 코드는 실행되지 않는다.
}
코틀린의 try 키워드는 if나 when과 마찬가지로 식이다. 따라서 try의 값을 변수에 대입할 수 있다.
다른 문장과 마찬가지로 try의 본문도 내부에 여러 문장이 있으면 마지막 식의 값이 전체 결과 값이다.
2.6 요약
- 함수를 정의할 때 fun 키워드를 사용한다.
- val, var는 각각 읽기 전용 변수와 변경 가능한 변수를 선언할 때 쓰인다.
- 문자열 템플릿을 사용하면 문자열을 연결하지 않아도 되므로 코드가 간결해진다.
- 변수 이름 앞에 $를 붙이거나, 식을 ${식}처럼 ${ }로 둘러싸면 변수나 식의 값을 문자열 안에 넣을 수 있다.
- 코틀린에서는 값 객체 클래스를 아주 간결하게 표현할 수 있다.
- 다른 언어에도 있는 if는 코틀린에서 식이며, 값을 만들어낸다.
- 코틀린 when은 자바의 switch와 비슷하지만 더 강력하다.
- 어떤 변수의 타입을 검사하고 나면 굳이 그 변수를 캐스팅하지 않아도 검사한 타입의 변수처럼 사용할 수 있다. 그런 경우 컴파일러가 스마트 캐스트를 활용해 자동으로 타입을 바꿔준다.
- for, while, do-while 루프는 자바가 제공하는 같은 키워드의 기능과 비슷하다. 하지만 코틀린의 for는 자바의 for 보다 더 편리하다. 특히 맵을 이터레이션하거나 이터레이션하면서 컬렉션의 원소와 인덱스를 함꼐 사용해야 하는 경우 코틀린의 for가 더 편리하다.
- 1..5와 같은 식은 범위를 만들어낸다. 범위와 수열은 코틀린에서 같은 문법을 사용하며, for 루프에 대해 같은 추상화를 제공한다. 어떤 값이 범위 안에 들어있거나 들어있지 않은지 검사하기 위해서 in이나 !in을 사용한다.
- 코틀린 예외 처리는 자바와 비슷하다. 다만 코틀린에서는 함수가 던질 수 있는 예외를 선언하지 않아도 된다.
참조
드미트리 제메로프 · 스베트라나 이사코바, 『Kotlin IN ACTION』, 오현석 옮김, 에이콘(2017), P77-101.