[Kotlin] 5-2. 프로그램의 흐름 제어, 예외 처리

leeeha·2022년 7월 7일
0

코틀린

목록 보기
14/28
post-thumbnail

출처: https://www.boostcourse.org/mo132/lecture/59984

흐름의 중단과 반환

흐름 제어문

  • return: 함수의 결과값을 반환하거나 지정된 라벨로 이동
  • break: for나 while의 조건식에 상관없이 반복문을 끝냄.
  • continue: for나 while 반복문의 본문을 모두 수행하지 않고 다시 조건으로 넘어감.

예외 처리문

  • try {...} catch {...}: try 블록의 본문을 수행하는 도중에 예외가 발생하면, catch 블록의 본문을 실행함.
  • try {...} catch {...} finally {...}: 예외가 발생해도 finally 블록 본문은 항상 실행됨.

return의 사용

보통 return문은 함수 등에서 값을 반환하는데 사용한다. 하지만 코드의 흐름을 중단하고 함수 등을 빠져 나가기 위해서도 return문을 사용할 수 있다.

return으로 값 반환하기

fun add(a: Int, b: Int): Int{
	return a + b
    println("이 코드는 실행되지 않습니다.") // 여기에 도달 X
}

return으로 Unit 반환하기

// 1. Unit을 명시적으로 반환
fun hello(name: String): Unit {
	println(name)
    return Unit 
}
// 2. Unit 이름을 생략한 반환 
fun hello(name: String): Unit {
	println(name)
    return 
}
// 3. return문 자체를 생략
fun hello(name: String) {
	println(name)
}

람다식에서 return 사용하기

package chap05.section3

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 // 10보다 크면 이 함수를 빠져 나감. (비지역 반환) 
        println("result: $result") // 10보다 크면 이 문장에 도달하지 못함.
    }
    println("end of retFunc")
} // 여기로 빠져 나감. 

fun main() {
    retFunc()
}

start of retFunc

inlineLambda 함수의 두 인자의 합이 10 이하가 되도록 하면, 아래와 같은 결과가 나온다.

start of retFunc
result: 6
end of retFunc

이처럼 람다식 내부에서 return을 사용하면, 람다식 외부의 함수까지 의도치 않게 종료되는 '비지역 반환'이 일어날 수 있기 때문에, 라벨 표기를 함께 사용해줘야 한다.

⭐ 람다식에서 라벨 사용하기

인라인(inline)으로 선언되지 않은 람다식에서 return을 사용할 때는 그냥 사용할 수 없으며, return@label과 같이 라벨(label) 표기와 함께 사용해야 한다. 라벨이란 코드에서 특정한 위치를 임의로 표시한 것으로, @ 기호 뒤에 이름을 붙여서 사용합니다.

package chap05.section3

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 -> // 1. 람다식 블록의 시작 부분에 라벨 지정
        val result = a + b
        if(result > 10) return@lit // 2. 라벨을 사용한 블록의 끝부분으로 반환 
        println("result: $result")
    } // 3. 이 부분으로 빠져 나감. 
    println("end of retFunc") // 4. 이 부분이 실행됨.
}

fun main() {
    retFunc()
}

start of retFunc
end of retFunc

암묵적 라벨

람다식 표현 블록에 라벨의 이름을 직접 지정하지 않고, 람다식 함수의 이름을 그대로 라벨처럼 사용할 수 있는데, 이를 암묵적 라벨이라고 한다.

package chap05.section3

fun inlineLambda(a: Int, b: Int, out: (Int, Int) -> Unit) { // inline 제거
    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")
}

fun main() {
    retFunc()
}

start of retFunc
end of retFunc

익명 함수를 사용한 반환

익명함수는 내부에서 라벨을 사용하지 않고 단순히 return만 사용하더라도 비지역 반환이 일어나지 않는다. 따라서 일반 함수의 반환처럼 편하게 사용할 수 있다.

package chap05.section3

fun inlineLambda(a: Int, b: Int, out: (Int, Int) -> Unit) { // inline 제거
    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") 
    }) // inlineLambda 함수의 끝 
    println("end of retFunc")
}

fun main() {
    retFunc()
}

start of retFunc
end of retFunc

람다식과 익명 함수의 비교

package chap05.section3

val getMessage = lambda@ { num: Int ->
    if(num !in 1..100){
        return@lambda "Error" // 레이블을 통한 반환
    }
    "Success" // 람다식은 return을 안 써줘도 마지막 식이 반환됨.
} // 여기로 빠져 나감.

val getMessage2 = fun(num: Int): String {
    if(num !in 1..100){
        return "Error"
    }
    return "Success" // 일반 익명 함수는 return을 꼭 써줘야 함.
}

fun main() {
    println(getMessage(5))
    println(getMessage(10000))

    println(getMessage2(5))
    println(getMessage2(10000))
}

Success
Error
Success
Error


break, continue

  • break: for나 while의 조건식에 상관없이 반복문을 끝냄.
  • continue: for나 while 반복문의 본문을 모두 수행하지 않고 다시 조건으로 넘어감.
package chap05.section3

fun main() {
    for(i in 1..5){
        if(i == 3) break
        print(i) // 12
    }
    println()
    println("outside") 
}

12
outside

package chap05.section3

fun main() {
    for(i in 1..5){
        if(i == 3) continue
        print(i) // 1245
    }
    println()
    println("outside")
}

1245
outside

break와 라벨을 함께 사용하기

라벨을 사용하지 않는 경우

fun labelBreak(){
    println("labelBreak")
    println("(i, j)")
    for(i in 1..5){
        for(j in 1..5){
            if(j == 3) break
            println("($i, $j)")
        }
        println("after for j")
    }
    println("after for i")
}

labelBreak
(i, j)
(1, 1)
(1, 2)
after for j
(2, 1)
(2, 2)
after for j
(3, 1)
(3, 2)
after for j
(4, 1)
(4, 2)
after for j
(5, 1)
(5, 2)
after for j
after for i

라벨을 사용하는 경우

fun labelBreak2(){
    println("labelBreak2")
    println("(i, j)")

    first@ for(i in 1..5){
        second@ for(j in 1..5){
            if(j == 3) break@first // 빠져나갈 위치를 라벨로 지정
            println("($i, $j)")
        }
        println("after for j")
    } // 여기로 빠져 나감.

    println("after for i")
}

labelBreak2
(i, j)
(1, 1)
(1, 2)
after for i

continue문도 확인해보기

fun labelContinue(){
    println("labelContinue")
    println("(i, j)")
    for(i in 1..5){
        for(j in 1..5){
            if(j == 3) continue
            println("($i, $j)")
        }
        println("after for j")
    }
    println("after for i")
}

labelContinue
(i, j)
(1, 1)
(1, 2)
(1, 4)
(1, 5)
after for j
(2, 1)
(2, 2)
(2, 4)
(2, 5)
after for j
(3, 1)
(3, 2)
(3, 4)
(3, 5)
after for j
(4, 1)
(4, 2)
(4, 4)
(4, 5)
after for j
(5, 1)
(5, 2)
(5, 4)
(5, 5)
after for j
after for i

fun labelContinue2(){
    println("labelContinue2")
    println("(i, j)")

    first@ for(i in 1..5) {
        second@ for (j in 1..5) {
            if (j == 3) continue@first // j가 3이면 
            println("($i, $j)")
        }
        println("after for j")
    } // 여기로 이동함.

    println("after for i")
}

labelContinue2
(i, j)
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
(4, 1)
(4, 2)
(5, 1)
(5, 2)
after for i


예외 처리

에러와 예외

프로그램 코드를 작성하다 보면 해당 코드가 제대로 작동하지 못하고 중단되는 현상이 발생할 수 있는데, 이를 예외(exception)라고 한다. 대부분의 에러(error)는 코드를 작성하는 도중에 컴파일러가 잡아낼 수 있다. 하지만 메모리 부족이나 파일이 손상되는 등의 실행 도중의 잠재적인 오류까지 검사할 수 없기 때문에 정상적으로 실행이 되다가 비정상적으로 프로그램이 종료되는 경우를 예외라고 한다.

  • 운영체제의 문제 (잘못된 시스템 호출)
  • 입력값의 문제 (존재하지 않는 파일, 숫자 입력란에 문자 입력 등)
  • 받아들일 수 없는 연산 (0으로 나누기 등)
  • 메모리의 할당 실패 및 부족
  • 컴퓨터 기계 자체의 문제 (전원 문제, 망가진 기억 장치 등)

0으로 나누었을 때의 예외 처리

package chap05.section4

fun main() {
    val a = 6
    val b = 0
    val c: Int

    try{
        c = a / b // 0으로 나눔.
    }catch (e: Exception){
        println("Exception is handled.")
    }finally {
        println("finally 블록은 항상 반드시 실행됨.")
    }
}

Exception is handled.
finally 블록은 항상 반드시 실행됨.

특정 예외 처리

package chap05.section4

fun main() {
    val a = 6
    val b = 0
    val c: Int

    try{
        c = a / b // Division by zero
    }catch (e: ArithmeticException){ // 산술 연산에 대한 예외 처리 
        println("Exception is handled. ${e.message}")
    }finally {
        println("finally 블록은 항상 반드시 실행됨.")
    }
}

Exception is handled. / by zero
finally 블록은 항상 반드시 실행됨.

fun main() {
    val a = 6
    val b = 0
    val c: Int

    try{
        c = a / b // Division by zero
    }catch (e: Exception){
        e.printStackTrace() // 스택 추적하기 
    }finally {
        println("finally 블록은 항상 반드시 실행됨.")
    }
}

java.lang.ArithmeticException: / by zero
at chap05.section4.TryCatchKt.main(TryCatch.kt:9)
at chap05.section4.TryCatchKt.main(TryCatch.kt)
finally 블록은 항상 반드시 실행됨.

throw로 예외 발생시키기

package chap05.section4

fun main() {
    var amount = 600
    try{
        amount -= 100
        checkAmount(amount)
    }catch (e: Exception){
        println(e.message)
    }
    println("amount: $amount")
}

fun checkAmount(amount: Int) {
    if(amount < 1000){
        throw Exception("잔고가 ${amount}으로, 1000 이하입니다.")
    }
}

잔고가 500으로, 1000 이하입니다.
amount: 500

profile
꾸준히!

0개의 댓글