Kotlin - 프로그램의 흐름 제어
조건문
if 문
if (조건식) {
...
} else if (조건식) {
...
} else {
...
}
다른 언어의 if문과 다를게 없습니다.
다른점이 있다면 if문을 하나의 표현식으로서 변수에 할당해 같은 기능을 수행하도록 할 수 있습니다.
val max = if(a > b) a else b
// 표현식이 길어지면 중괄호로 감싸야 합니다.
val max = if (a > b) {
println("a 선택")
a // 마지막 식인 a가 반환되어 max에 할당
} else {
println("b 선택")
b // 마지막 식인 b가 반환되어 max에 할당
}
간단하게 성적 판별 예시를 하나 보겠습니다.
fun main() {
val score = readLine()!!.toDouble() // 사용자 입력
var grade: Char = 'F'
if (score>= 90)
grade = 'A'
else if (score in 80.0..89.9) // 80 <= score <= 89.9
grade = 'B'
else
grade = 'C'
println(grade)
}
when문
when (인자) {
인자에 일치하는 값 혹은 표현식 -> 수행할 문장
인자에 일치하는 범위 -> 수행할 문장
...
else -> 수행할 문장
}
when은 자바의 switch문과 유사하지만 더 유연한 문법을 제공합니다.
when 블록의 안을 보면 화살표 왼쪽에는 조건을 나타내고 오른쪽을 수행할 문장을 사용합니다.
fun main() {
var x = 1
// .. 표현을 in문을 통해 사용 가능
when(x) {
1 -> println("x == 1")
2 -> println("x == 2")
3,4 -> println("x is 3 or 4")
in 5..10 -> println("x in 5..10")
!in 5..10 -> println("x !in 5..10")
else -> println("어떠한 범위에도 없다.")
}
// is도 사용 가능
var str = "hello"
when(str) {
is String -> println("문자열이다.")
else -> println("문자열이 아니다.")
}
// 인자 없이도 조건이나 표현식을 직접 만들어서 사용
when {
x ==1 -> println("x == 1")
x == 2 -> println("x == 2")
x in 5..10 -> println("x in 5..10")
x !in 5..10 -> println("x !in 5..10")
else -> println("어떠한 범위에도 없다.")
}
}
when 문의 인자로서 Any를 사용하게 되면 다양한 자료형의 인자를 받을 수 있습니다.
fun main() {
case("hello")
}
fun case(obj: Any) {
when(obj){
1 -> println(" is one")
"hello" -> println(" is hello")
!is String -> println(" is not String")
}
}
반복문
for 문
for (요소 변수 in 컬렉션 또는 범위) { 반복할 본문}
코틀린의 for문은 자바보다는 파이썬의 for문과 비슷합니다.
fun main() {
for (x in 1..5)
println(x)
for (x in 1..5 step 2) // 2칸씩 이동
println(x)
for (x in 5..1) // 아무것도 출력되지 않음
println(x)
for (x in 5 downTo 1) // 줄어들기
println(x)
for (x in 5 downTo 1 step 2) // 2칸씩 줄어들기
println(x)
}
while 문
while (조건식) {
본문
}
do {
본문
} while (조건식)
while문은 자바와 다를게 없기에 넘어가겠습니다.
흐름의 중단과 반환
- 흐름 제어문
- return : 함수에서 결과값을 반환하거나 지정된 라벨로 이동합니다.
- break : for문이나 while문의 조건식에 상관없이 반복문을 끝낸다.
- continue : for문이나 while문의 본문을 모두 수행하지 않고 다시 조건식으로 넘어간다.
- 예외 처리문
- try catch : try 블록의 본문을 수행하는 도중 예외가 발생하면 catch 블록의 본문을 실행한다.
- try catch finally : 예외가 발생해도 finally 블록 본문은 실행한다.
return 문
fun add(a: Int, b: Int): Int {
return a+b
println("여기는 실행되지 않습니다.")
}
fun welcome() { // Unit 반환 생략
println("어서오세요")
}
Unit과 return을 생략할 경우 코틀린 컴파일러는 Unit을 반환하는 것으로 가정합니다.
cf) 람다식에서 return, break, continue 사용 가능 여부
람다식에서 return은 라벨 표기와 함께 사용해야만 사용할 수 있고 break, continue문은 아직 지원되지 않습니다.
람다식에서 return 사용하기
앞선 포스팅에서 보았듯이 람다식에서는 return문을 사용할 수 없고 인라인 된 람다식에서만 특별하게 return문을 사용할 수 있었습니다.
기본 람다식에서도 return문을 사용할 순 있는데 이때는 라벨과 함께 사용해야 합니다.
라벨이란 코드에서 특정한 위치를 임시로 표기한 것으로 @기호화 이름을 붙여서 사용합니다.
인라인으로 선언된 함수에서 람다식을 매개변수로 사용하면 람다식에서 return을 사용할 수 있습니다.
fun main() {
retFunc()
}
inline fun inlineLambda(a: Int, b: Int, out: (Int, Int) -> Unit) {
out(a, b)
}
fun retFunc() {
println("start of retFunc")
inlineLambda(13, 3) { a, b ->
val result = a + b
if (result > 10) return // inlineLambda가 인라인 함수이므로 리턴문 사용 가능
println("result : $result")
}
println("end of retFunc")
}
람다식에서 라벨과 함께 return 사용하기
람다식 함수 이름 라벨 이름@ {
...
return@라벨 이름
}
앞선 예제와 같지만 inline을 제거하고 return 문을 라벨과 같이 사용해 보겠습니다.
fun main() {
retFunc()
}
fun inlineLambda(a: Int, b: Int, out: (Int, Int) -> Unit) { // inline 제거
out(a, b)
}
fun retFunc() {
println("start of retFunc")
inlineLambda(13, 3) lit@{ a, b -> // 람다식을 라벨로 지정
val result = a + b
if (result > 10) return@lit // 해당 람다식을 종료
println("result : $result")
} // 람다식 리턴시 여기로 이동
println("end of retFunc")
}
암묵적 라벨
람다식 표현식 블록에 직접 라벨을 쓰는 것이 아닌 람다식 명칭을 그대로 라벨처럼 사용할 수 있는데 이것을 암묵적 라벨이라고 합니다.
fun main() {
retFunc()
}
fun inlineLambda(a: Int, b: Int, out: (Int, Int) -> Unit) {
out(a, b)
}
fun retFunc() {
println("start of retFunc")
inlineLambda(13, 3) { a, b -> //
val result = a + b
if (result > 10) return@inlineLambda // 함수 이름을 라벨로 사용
println("result : $result")
} // 람다식 리턴시 여기로 이동
println("end of retFunc")
}
익명 함수를 사용한 반환
람다식 대신 익명 함수를 사용할 경우에는 라벨 없이 가까운 익명 함수 자체가 반환되므로 return을 바로 사용해도 됩니다.
fun main() {
retFunc()
}
fun inlineLambda(a: Int, b: Int, out: (Int, Int) -> Unit) {
out(a, b)
}
fun retFunc() {
println("start of retFunc")
inlineLambda(13, 3, fun(a,b) {
val result = a + b
if (result > 10) return // 익명 함수 리턴
println("result : $result")
}) // 리턴시 여기로 이동
println("end of retFunc")
}
val getMessage = lambda@ { num: Int ->
if(num !in 1..100){
return@lambda "error" // getMessage에 error 대입
}
"success" // 기본은 가장 마지막 식이 리턴
}
람다식을 사용하면 return문을 사용할 수 없고 마지막 식이 리턴되기 때문에 사실상 return문이 2개인데 가독성이 떨어집니다.
따라서 return과 같이 명시적으로 반환해야 할 것이 여러 개라면 람다식보다는 익명 함수를 사용하는 것이 가독성이 좋습니다.
val getMessage = fun(num: Int): String {
if(num !in 1..100){
return "error"
}
return "success"
}
람다식과 익명 함수를 함수에 할당할 때 주의할 점
람다식은 특정 함수에 할당할 때 주의하여 사용해야 합니다.
fun greet() = {println("Hello")}
greet() // 아무것도 출력되지 않음
greet()() // Hello 찍힘
할당 연산자(=) 에 의해 람다식 자체가 greet() 함수에 할당된 것이기 때문에 greet()가 가진 함수를 사용하려면 ()를 한번 더 사용해야 합니다.
fun greet() = fun() {println("Hello")}
함수가 할당됨을 명시적으로 표현하려면 익명 함수를 사용하는 것이 더 가독성이 좋을 수 있습니다.
break문과 continue문
break와 continue의 사용법은 java와 다를게 없으므로 라벨과 함께 사용하는 부분만 살펴봅시다.
fun labelBreak() {
first@ for (i in 1..5){
second@ for (j in 1..5){
if (j == 3) break@first
}
} // break시 first라벨에 해당하는 for문 바깥으로 이동한다.
}
fun labelContinue() {
first@ for (i in 1..5){
second@ for (j in 1..5){
if (j == 3) continue@first
}
} // continue시 first라벨에 해당하는 for문의 continue가 진행된다.
}
예외 처리
try {
예외 발생 가능성 문장
// throw Exception("message")
} catch (e: 예외 처리 클래스){
예외 처리
} finally {
반드시 실행되어야 하는 문장
}
예외처리 블록은 자바와 다를게 없습니다.