in 연산자를 사용해 어떤 값이 범위에 속하는지 검사할 수 있따. 반대로 !in을 사용하면 어떤 값이 범위에 속하지 않는지 검사할 수 있다. 다음은 어떤 문자가 정해진 문자의 범위에 속하는지 검사하는 방법이다. 단 범위가 정말 크다면 이러한 방법을 쓰는건 개인적으로 추천하지 않는다.
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c in '1'..'9'
fun main() {
println(isLetter('z'))
println(isNotDigit('2'))
}
이런 식으로 in을 통해 특정 원소가 어떤 범위 내에서 속하는지에 대한 함수를 만들 수 있다. 그러면 이를 when을 사용하여 만들어보자.
fun recognize(c: Char) = when(c) {
in 'a'..'z', in 'A'..'Z' -> "${c}는 영어문자입니다."
in '1'..'9' -> "${c}는 숫자입니다."
else -> "영어도 숫자도 아닌 문자입니다."
}
fun main() {
println(recognize('%'))
}
범위는 프로그래머가 어떤 범위를 지정하는지에 대한 자유도가 높기 때문에 확인하고 싶은 원소를 만든 후 집합이든 리스트이든 만들어서 진행하면 된다. 하지만, 여기서 중요한 것은 Comparable을 사용하는 경우 그 범위 내의 모든 객체를 항상 이터레이션하지는 못한다. 예를 들어서 ‘Java’와 ‘Kotlin 사이의 모든 문자열을 이터레이션할 수 있을까? 그럴 수 없다. 하지만 in 연산자를 사용하면 값이 범위 안에 속하는지 항상 결정할 수 있다. 이는 매우 중요한 코틀린의 문법 중 하나이다. 다음의 코드를 보고 확인할 수 있다.
fun main() {
println("Kotlin" in "Java".."Scala")
}
위 코드는 “Java” ≤ “Kotlin” && “Kotlin” ≤ “Scala”
와 같은 의미를 지니고 있다. 위와 같이 굳이 문자열 뿐만 아니라 집합에서도 in을 사용하여 포함관계를 확인할 수 있다. (개인적으로 이런 부분은 너무 좋다. Python을 보는 것 같은 이런 반가움이..!)
코틀린의 예외 처리도 자바나 다른 언어의 예외 처리와 비슷하다. 함수는 정상적으로 종료할 수 있으나 오류가 발생하면 예외를 던질(throw) 수 있다. 함수를 호출하는 쪽에서는 그 예외를 잡아 처리할 수 있다. 발생한 예외를 함수 호출 단에서 처리(catch)하지 않으면 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올 때 까지 예외를 다시 던진다.
또한 코틀린의 예외처리 구문은 자바와 매우 비슷한다. 아래의 코드를 보자.
if (percentage !in 0..100) {
throw IllegalArgumentException("A percentage value must be between 0 and 100: $percentage")
}
물론 예외 인스턴스를 만들 때 new를 붙일 필요가 없다. 자바와 달리 kotlin의 throw는 식이므로 다른 식에 포함될 수 있다. 아래와 같이 말이다.
val percentage = if (number in 0..100) number else throw IllegalArgumentException("A percentage value must be between 0 and 100: $number")
위 예제를 통해 percentage라는 값은 number가 0과 100을 포함한 사이에 존재하는 수 이면 초기화 되지만 그렇지 않으면 초기화 되지 않는다.
자바와 마찬가지로 에외를 처리하려면 try와 catch, finally 절을 함께 사용한다. try, catch, finally를 사용하기 전에 파일에서 각 줄을 읽어 수로 변환하되 그 줄이 올바른 수 형태가 아니면 null을 반환하는 함수를 만들어보자.
import java.io.*
fun readNumber(read: BufferedReader): Int? {
try {
val line = read.readLine()
return Integer.parseInt(line)
}
catch (e: NumberFormatException) {
return null
}
finally {
read.close()
}
}
fun main() {
val reader = BufferedReader(StringReader("239"))
println(readNumber(reader))
}
위 코드와 java와의 가장 큰 차이는 throws 절이 코드에 없다는 점이다. 자바에서는 함수를 작성할 떄 함수 선언 뒤에 throws IOException을 붙여야 한다. 이유는 IOException이 체크 예외(Checked exception)이기 때문이다. 자바에서는 체크 예외를 명시적으로 처리해야 한다. 어떤 함수가 던질 가능성이 있는 예외나 그 함수가 호출한 다른 함수에서 발생할 수 있는 예외를 모두 catch로 처리해야 하며, 처리하지 않은 예외는 throws 절에 명시해야 한다. 다른 최신 JVM 언어와 마찬가지로 코틀린도 체크 예외와 언체크 예외 unchecked exception을 구별하지 않는다. 즉 코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아내도 되고 잡아내지 않아도 된다. 자바에서는 프로그래머에게 예외처리를 강제한다. 하지만 코틀린의 경우 그렇지 않다.
다음은 finally 절을 없애고 파일에서 읽은 수를 출력하는 코드를 추가하자.
import java.io.*
fun readNumber(read: BufferedReader) {
val number = try {
Integer.parseInt(read.readLine())
} catch (e: NumberFormatException) {
return
}
println(number)
}
fun main() {
val reader = BufferedReader(StringReader("Not a number"))
println(readNumber(reader))
}
위 코드를 실행했을 때 아무것도 출력되지 않는 것을 확인할 수 있다. 코틀린의 try 키워드는 if나 when과 마찬가지로 식이다. 따라서 try값을 변수에 대입할 수 있다. 마지막으로 catch 부분에서 null을 출력하는 함수를 만들어 보자.
import java.io.*
fun readNumber(read: BufferedReader) {
val number = try {
Integer.parseInt(read.readLine())
} catch (e: NumberFormatException) {
null
}
println(number)
}
fun main() {
val reader = BufferedReader(StringReader("Not a number"))
println(readNumber(reader))
}