코틀린[Kotlin] #03_코틀린 기초(when, 스마트 캐스트, 반복문, in, 예외처리)
안녕하세요. 문범우입니다.
오늘은 지난번 포스팅에 이어서, 코틀린의 기초에 대해서 알아봅니다.
보다 자세하게는 when, 스마트캐스트, 반복문, in, 예외처리에 대해서 학습하도록 하겠습니다.
관련된 코드의 내용은 아래 주소에서 확인하실 수 있습니다.
https://github.com/doorBW/kotlin-study
1. when
먼저 지난 시간에 구성했던 enum class, Color를 이용하여 when에 관한 함수를 만들어보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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); // need semicolon fun rgb() = (r * 256 + g) * 256 + b } 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)) // Battle } | cs |
위에서 만든 getMnemonic 함수를 살펴보자. 우선 등호를 이용하여 식이 본문인 함수로 만들었다. 즉 when 또한 식이라는 것을 알 수 있다.
when 뒤에는 소괄호를 이용하여 color 값을 받는다. 그리고 해당 color가 enum상수와 동일한 것을 찾아서 화살표를 통해 반환할 값을 지정하고 있다.
또한, 하나의 분기(조건) 안에서 여러 값을 매치할 수 있다. 이러한 경우 다음의 코드와 같이 각 분기를 콤마(,)로 연결해주면 된다.
1 2 3 4 5 6 7 8 9 10 11 | 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>){ println(getWarmth(Color.ORANGE)) // warm } | cs |
물론 조건에 해당 하는 값이 없는 경우를 위해 else를 두어 처리할 수 있다.
또한 위와 같이 when 뒤에 인자를 붙이는 것이 필수는 아니다. 단순히 when 이후에 중괄호로 시작하여 바로 조건문을 통해 케이스 분류를 할 수 있다.
2. 스마트 캐스트
코틀린에서 스마트 캐스트는 타입 검사와 타입 캐스트를 조합하여 진행된다. 즉, 타입 검사 이후 별도의 타입 캐스팅이 필요없다는 의미이다.
이를 확인하기 위해 다음과 같은 코드를 작성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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) { // smart casting val n = e as Num return n.value } if (e is Sum) { // smart casting return eval(e.right) + eval(e.left) } throw IllegalArgumentException("Unknown expression") } fun main(args: Array<String>){ println(eval(Sum(Sum(Num(1), Num(2)), Num(4)))) // 7 } | cs |
Expr 이라는 인터페이스를 선언하고, 이를 상속받는 Num class와 Sum class를 생성하였다.
이후 eval이라는 함수는 Expr 타입의 객체를 파라미터로 받아서 이때 Num 객체라면 해당 객체의 value를 반환하고, Sum 객체라면 left와 right객체를 다시 eval함수로 넘긴다음 반환 값을 더한다.
오히려 말로 설명하는 것이 복잡할 수 있으니 코드를 살펴보면 이해가 빠르다.
이때, 사용된 if문, 6번 라인과 10번 라인을 보면 is 라는 키워드를 이용해 인자의 타입을 검사하며 바로 캐스팅하는 스마트 캐스트를 볼 수 있다.
코드상에서는 설명을 위해 7번라인에 타입 캐스트를 하는 코드가 있다. e라는 값을 Num 객체로 캐스트 하여 변수 n으로 받는 코드이다. 하지만 이와 다르게 Sum인지를 확인하는 if문 내부에는 그러한 코드가 없다. 7번 라인과 같이 타입을 캐스팅하는 역할을 is 키워드를 통해 타입 검사와 타입 캐스팅을 동시에 처리하기 때문이다.
위의 코드를 앞에서 배운 when을 이용해 다음과 같이 변경시킬 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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.left) + eval(e.right) else -> throw IllegalArgumentException("Unknown expression") } fun main(args: Array<String>){ println(eval(Sum(Sum(Num(1), Num(2)), Num(4)))) // 7 } | cs |
3. 반복문
코틀린에서의 반복문은 자바와 매우 유사하다.
실제로 반복문 while은 다음과 같이 자바와 동일하므로 따로 다루지 않겠다.
1 2 3 4 5 6 7 | while(조건) { /* ... */ } do { /* ... */ } while(조건) | cs |
하지만 for 루프에서는 자바와 약간의 차이점이 있다. 오히려 자바에서 사용되는 for-each문이 코틀린의 기본 for와 비슷한 형태를 띄고있다고 생각하면 좋다.
이때 코틀린에서 사용되는 연산자가 등장한다. 범위(range)를 표현하는 연산자로써 시작하는 숫자와 끝나는 숫자 사이에 .. 을 붙여주면 된다. 다음은 해당 연산자를 이용하여 1부터 10까지 출력하는 예제이다.
1 2 3 4 5 6 7 8 9 | fun main(args: Array<String>){ for(i in 1..10){ println(i) // 1 // 2 // .. // 10 } } | cs |
위의 코드와 같이 범위를 표현할 때에는 .. 연산자를 이용한다. 그리고 for문을 이용할 때에는 받아서 처리하는 값 뒤에 in 키워드를 붙이고 그 뒤에 범위값이나 리스트 요소를 배치하여 사용한다.
헌데 만약 범위를 이용하여 표현할 때, 값이 1씩 증가하는게 아니라 2씩 증가하는 for문을 작성하고 싶다면 어떻게 할까? 아니면 값이 감소하는 for문은?
증가하는 값에 대한 정보는 추가로 step을 통해 표현하고, 값이 감소되는 것은 downTo를 이용하여 다음과 같이 표현한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | fun main(args: Array<String>){ for(i in 1..100 step 3){ println(i) // 1 // 4 // .. // 97 // 100 } for(i in 100 downTo 1){ println(i) // 100 // 99 // .. // 1 } } | cs |
4. In
in 연산자는 for문에서 사용된 것과 다르게 어떠한 값이 범위에 속하는지 검사하는 용도로 사용될 수 있다. 반대로 !in 을 사용하여 어떠한 값이 범위에 속하지 않는지도 검사할 수 있다.
1 2 3 4 5 6 7 | fun main(args: Array<String>){ fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' println(isLetter('k')) // true println(isLetter('0')) // false } | cs |
위와 같이 간단한 isLetter 함수를 만들어서 활용해 보았다.
또한 우리가 앞에서 학습한 when과 함께 이용하는 것도 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12 | 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." } fun main(args: Array<String>){ println(recognize('8')) // It's a digit! println(recognize('t')) // It's a letter! } | cs |
5. 예외 처리
코틀린의 예외처리 또한 자바와 비슷하다. 함수에서 오류가 발생하면 해당 예외를 throw할 수 있다. 그리고 함수를 호출하는 부분에서 해당 예외를 잡아서 처리할 수 있다.
물론 이전과 같이 예외 인스턴스를 만들때 new 키워드를 붙이지 않아도 된다. 또한 한가지 자바와 다른 점은, 함수가 던질 수 있는 예외를 직접 명시하지 않아도 되는 점이다.
자바에서는 체크 예외를 명시적으로 처리했어야 했다. 어떤 함수 안에서 발생될 수 있는 예외에 대해서 catch로 처리하거나 처리하지 않은 예외는 함수의 끝에 throws로 명시해야 했지만, 코틀린은 그렇지 않다.
코틀린에서의 예외 처리는 다음 코드와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | fun readNumber (reader: String){ val number = try { Integer.parseInt(reader) } catch (e: NumberFormatException){ null }finally { println("readNumber func end.") } println(number) } fun main(args: Array<String>){ readNumber("5") // readNumber func end. // 5 readNumber("t") // readNumber func end. // null } | cs |
- Quiz
Q1. 코틀린에서 스마트 캐스트란 무엇이고 어떤 키워드를 사용하나?
Q2. 범위(range)를 나타내는 연산자는?
참고 서적 및 링크
* [Kotlin in Action] - 드미트리 제메로프/스베트라나 이사코바 지음, 오현석 옮김, 에이콘 출판사