Study(종료)/Kotlin 22.09.13 ~ 12.18

3-4. 함수 정의하기 : 예외 처리

Ski_ 2022. 9. 26. 12:51

3장 정리

3-1. 함수 정의하기 : 함수

3-2. 함수 정의하기 : 조건문

3-3. 함수 정의하기 : 루프

3-4. 함수 정의하기 : 예외 처리


3.5 예외 처리

함수가 비정상적으로 오류가 발생한 경우 예외를 던질 수 있다(Throw Exception)

예외가 발생한 경우, 함수를 호출한 쪽에서 예외를 잡아내거나(catch), 함수 호출 스택의 위로 예외가 전달될 수 있다.

1) 예외 던지기

에외를 던질 때는 아래와 같은 일이 벌어진다.

- 프로그램은 예외를 잡아내는 핸들러(excption handler)를 찾는다. 예외와 일치하는 핸들러가 있다면 그 핸들러가 에외를 처리한다.

- 현재 함수 내부에서 핸들러를 찾을 수 없다면 함수 실행이 종료되고 함수가 스택에서 제거(pop)된다.

 그리고 함수를 호출한 문맥 안에서 예외 핸들러 검색을 수행한다. 이런 경우 예외를 호출자에게 전파(propagate)했다고 말한다.

- 프로그램 진입점에 이를 때 까지 예외를 잡아내지 못하면(예외 핸들러가 없다면) 현재 스레드가 종료된다.

 

문자열이 잘못된 경우 어떤 값을 돌려주는 대신 오류를 발생시키도록 해보자

fun parseIntNumber(s: String): Int{
    var num = 0;
    if(s.length !in 1..31) throw NumberFormatException("Not a number: $s")
    for(c in s){
        if(c !in '0'..'9') throw NumberFormatException("Not a number: $s")
        num = num*10 + (c - '0')
    }
    return num
}

fun main() {

//    val a = "foo"
//    println(parseIntNumber(a)) 
//	  NumberFormatException: Not a number: foo

//    val b = "20s"
//    println(parseIntNumber(b))
//    NumberFormatException: Not a number: 20s

    val c = "109"
    println(parseIntNumber(c))
//  109    
}

2) try 문으로 예외 처리하기

코틀린에서 예외를 처리할 때는 try - catch 문을 사용한다.

위 코드에서 주석처리된 부분을 실행하면, 다음 코드가 실행되지 않고 비정상 종료된다.

이제 이 부분을 실행해도 비정상종료되지 않고 다음 코드가 실행되게 변경해보자.

import java.lang.NumberFormatException

fun parseIntNumber(s: String): Int{
    var num = 0
    try{
        if(s.length !in 1..31) throw NumberFormatException("Not a number: $s")
        for(c in s){
            if(c !in '0'..'9') throw NumberFormatException("Not a number: $s")
            num = num*10 + (c - '0')
        }
    }catch (e: NumberFormatException){
        println(e.toString())
        num = Integer.MIN_VALUE
    }
    return num
}

fun main() {

    val a = "foo"
    val b = "20s"
    val c = "109"

    println(parseIntNumber(a))
    // java.lang.NumberFormatException: Not a number: foo
	// -2147483648
    println(parseIntNumber(b))
    // java.lang.NumberFormatException: Not a number: foo
	// -2147483648
    println(parseIntNumber(c))
    // 109
}

코드가 마지막줄까지 도달하는 것을 볼 수 있다.

 

catch 블록은 선언된 순서대로 예외 타입을 검사하기 때문에

어떤 타입을 처리할 수 있는 catch 블록을 그 타입의 상위 타입을 처리할 수 있는 catch 블록보다 앞에 작성해야 한다.

그렇지 않으면 상위 타입을 잡아내는 핸들러가 하위 타입인 예외도 모두 잡아내버린다.

fun parseIntNumber(s: String): Int{
    var num = 0
    try{
        if(s.length !in 1..31) throw NumberFormatException("Not a number: $s")
        for(c in s){
            if(c !in '0'..'9') throw NumberFormatException("Not a number: $s")
            num = num*10 + (c - '0')
        }
    }catch (e: Exception){
        println(e.toString())
        num = Integer.MIN_VALUE
    }catch (e: NumberFormatException){ // 죽은 코드
        println(e.toString())
        num = Integer.MAX_VALUE
    }
    return num
}

위 코드에서 NumberFormatException은 Exception의 하위 타입이기 떄문에

두 번째 catch 블록인 NumberFormatException은 실제로는 죽어있는 코드이다.

 

try 문에 finally 블록을 사용할 수 있다. finally 블록은 try 블록은 떠나기 전에 프로그램이 어떤 일을 수행하도록 만들어준다.

주로 try 블록 앞이나 내부에서 할당한 자원을 해제할 때 유용하다.

예를 들어 파일, 네트워크 연결을 닫거나 DB와 관련된 자원을 해제하는데 사용한다.

 

코틀린의 try 블록은 식이다. 이 식의 값은(예외가 발생하지 않은 경우) try 블록의 값이거나 예외를 처리한 catch 블록의 값이 된다.

import java.lang.NumberFormatException

fun readInt(default: Int) = try{
    readLine()!!.toInt()
}catch (e: NumberFormatException){
    default
}finally {
    println("END")
}

fun main() {
    val a = readInt(10)
    // 1000 입력
    println(a)
    // 1000
    // END
}

여기까지가 이번주 스터디 내용이다.

다음주에는 4장 클래스와 객체 다루기 관련 내용을 작성할 예정이다.

반응형