백고등어 개발 블로그
코틀린 강의 7강: 예외 처리 본문
7강: 예외 처리
예외란?
예외(Exception)는 프로그램 실행 중에 발생하는 비정상적인 상황을 나타냅니다. 파일을 찾을 수 없거나, 네트워크 연결이 끊기거나, 0으로 나누기를 시도하는 등의 상황에서 예외가 발생합니다.
try-catch-finally
기본 문법
fun main() {
try {
val result = 10 / 0 // ArithmeticException 발생
println(result)
} catch (e: ArithmeticException) {
println("0으로 나눌 수 없습니다: ${e.message}")
} finally {
println("finally 블록은 항상 실행됩니다")
}
}
finally 블록은 예외 발생 여부와 관계없이 항상 실행되며, 주로 리소스 정리 작업에 사용됩니다.
여러 예외 처리
fun parseNumber(str: String) {
try {
val number = str.toInt()
val result = 100 / number
println("결과: $result")
} catch (e: NumberFormatException) {
println("숫자 형식이 잘못되었습니다")
} catch (e: ArithmeticException) {
println("산술 연산 오류가 발생했습니다")
} catch (e: Exception) {
println("알 수 없는 오류가 발생했습니다: ${e.message}")
}
}
fun main() {
parseNumber("abc") // 숫자 형식이 잘못되었습니다
parseNumber("0") // 산술 연산 오류가 발생했습니다
}
try를 표현식으로 사용
코틀린의 try는 표현식이므로 값을 반환할 수 있습니다:
fun readNumber(str: String): Int? {
return try {
str.toInt()
} catch (e: NumberFormatException) {
null
}
}
fun main() {
val num1 = readNumber("123")
val num2 = readNumber("abc")
println(num1) // 123
println(num2) // null
}
표현식으로 사용할 때는 try 블록이나 catch 블록의 마지막 값이 반환됩니다:
fun calculateResult(a: Int, b: Int): String {
return try {
val result = a / b
"결과: $result"
} catch (e: ArithmeticException) {
"계산 불가"
}
}
fun main() {
println(calculateResult(10, 2)) // 결과: 5
println(calculateResult(10, 0)) // 계산 불가
}
예외 던지기(throw)
throw 키워드로 명시적으로 예외를 발생시킬 수 있습니다:
fun validateAge(age: Int) {
if (age < 0) {
throw IllegalArgumentException("나이는 0 이상이어야 합니다")
}
println("나이: $age")
}
fun main() {
try {
validateAge(25) // 나이: 25
validateAge(-5) // 예외 발생
} catch (e: IllegalArgumentException) {
println("오류: ${e.message}")
}
}
throw도 표현식
throw도 표현식이므로 Elvis 연산자와 함께 사용할 수 있습니다:
fun getUsername(name: String?): String {
return name ?: throw IllegalArgumentException("이름은 null일 수 없습니다")
}
fun main() {
println(getUsername("김코틀린")) // 김코틀린
// println(getUsername(null)) // 예외 발생
}
Nothing 타입
throw와 예외를 던지는 함수는 Nothing 타입을 반환합니다. Nothing은 "값이 없음"을 나타내는 특수한 타입입니다:
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
fun main() {
val name: String = readLine() ?: fail("이름을 입력하세요")
println("안녕하세요, $name님")
}
주요 예외 타입
IllegalArgumentException
잘못된 인자가 전달되었을 때 사용합니다:
fun setAge(age: Int) {
require(age >= 0) { "나이는 0 이상이어야 합니다" }
println("나이 설정: $age")
}
require() 함수는 조건이 false면 IllegalArgumentException을 던집니다.
IllegalStateException
객체의 상태가 메서드 호출에 적합하지 않을 때 사용합니다:
class BankAccount {
private var balance = 0
private var isOpen = true
fun withdraw(amount: Int) {
check(isOpen) { "계좌가 닫혀있습니다" }
check(balance >= amount) { "잔액이 부족합니다" }
balance -= amount
}
}
check() 함수는 조건이 false면 IllegalStateException을 던집니다.
NullPointerException
코틀린은 null 안전성을 제공하지만, !! 연산자 사용 시 NPE가 발생할 수 있습니다:
fun main() {
val name: String? = null
try {
println(name!!.length) // NullPointerException 발생
} catch (e: NullPointerException) {
println("null 값입니다")
}
}
Checked vs Unchecked 예외
자바와 달리 코틀린은 checked 예외를 구분하지 않습니다. 모든 예외는 unchecked이므로 throws 선언이 필요 없습니다:
// 자바에서는 throws IOException 선언 필요
// 코틀린에서는 필요 없음
fun readFile(path: String): String {
return java.io.File(path).readText()
}
이는 코드를 더 간결하게 만들지만, 어떤 예외가 발생할 수 있는지 문서화가 중요합니다.
리소스 자동 관리
use 함수
코틀린의 use 함수는 자바의 try-with-resources와 유사합니다:
import java.io.File
fun readFirstLine(path: String): String? {
return File(path).bufferedReader().use { reader ->
reader.readLine()
}
}
// use 블록이 끝나면 자동으로 close() 호출
use 함수는 Closeable 인터페이스를 구현한 모든 객체에 사용할 수 있습니다.
runCatching을 이용한 함수형 예외 처리
코틀린 1.3부터 runCatching 함수를 제공합니다:
fun parseNumber(str: String): Result<Int> {
return runCatching { str.toInt() }
}
fun main() {
val result1 = parseNumber("123")
val result2 = parseNumber("abc")
// 성공/실패 확인
println(result1.isSuccess) // true
println(result2.isFailure) // true
// 값 가져오기
println(result1.getOrNull()) // 123
println(result2.getOrNull()) // null
// 기본값 제공
println(result2.getOrDefault(0)) // 0
// 예외 처리
result1.onSuccess { println("성공: $it") }
result2.onFailure { println("실패: ${it.message}") }
}
체이닝
runCatching은 함수형 스타일로 체이닝할 수 있습니다:
fun divide(a: Int, b: Int): Result<Int> {
return runCatching { a / b }
}
fun main() {
val result = runCatching { "10".toInt() }
.map { it * 2 }
.mapCatching { divide(100, it).getOrThrow() }
.getOrElse { 0 }
println(result)
}
실전 예제
data class User(val name: String, val age: Int, val email: String)
class UserValidator {
fun validateUser(name: String?, age: Int?, email: String?): Result<User> {
return runCatching {
// 이름 검증
require(!name.isNullOrBlank()) { "이름은 필수입니다" }
// 나이 검증
requireNotNull(age) { "나이는 필수입니다" }
require(age in 0..150) { "나이는 0-150 사이여야 합니다" }
// 이메일 검증
require(!email.isNullOrBlank()) { "이메일은 필수입니다" }
require("@" in email) { "올바른 이메일 형식이 아닙니다" }
User(name, age, email)
}
}
}
fun main() {
val validator = UserValidator()
// 유효한 사용자
validator.validateUser("김코틀린", 25, "kotlin@example.com")
.onSuccess { println("사용자 생성 성공: $it") }
.onFailure { println("오류: ${it.message}") }
// 무효한 사용자
validator.validateUser("", 200, "invalid")
.onSuccess { println("사용자 생성 성공: $it") }
.onFailure { println("오류: ${it.message}") }
}
커스텀 예외
필요한 경우 커스텀 예외를 정의할 수 있습니다:
class InsufficientBalanceException(
val balance: Int,
val required: Int
) : Exception("잔액 부족: 현재 $balance원, 필요 ${required}원")
class BankAccount(private var balance: Int) {
fun withdraw(amount: Int) {
if (balance < amount) {
throw InsufficientBalanceException(balance, amount)
}
balance -= amount
println("출금 성공: ${amount}원, 잔액: ${balance}원")
}
}
fun main() {
val account = BankAccount(10000)
try {
account.withdraw(5000) // 성공
account.withdraw(8000) // 예외 발생
} catch (e: InsufficientBalanceException) {
println(e.message)
println("부족액: ${e.required - e.balance}원")
}
}
예외 처리 모범 사례
- 구체적인 예외를 먼저 처리: 상위 예외 클래스는 나중에 catch합니다.
- finally로 리소스 정리: 또는 use 함수를 사용합니다.
- 예외를 무시하지 마세요: 최소한 로그는 남깁니다.
- 적절한 예외 타입 사용: 상황에 맞는 예외를 던집니다.
- 문서화: 어떤 예외가 발생할 수 있는지 명시합니다.
마치며
이번 강의에서는 코틀린의 예외 처리에 대해 알아보았습니다. try를 표현식으로 사용할 수 있다는 점, runCatching을 통한 함수형 예외 처리, 그리고 코틀린이 checked 예외를 구분하지 않는다는 점을 기억하세요. 다음 강의에서는 제네릭에 대해 알아보겠습니다.
'코틀린 강의' 카테고리의 다른 글
| 코틀린 강의 9강: 코루틴 기초 (0) | 2025.10.28 |
|---|---|
| 코틀린 강의 8강: 제네릭 (0) | 2025.10.28 |
| 코틀린 강의 6강: 컬렉션 (0) | 2025.10.28 |
| 코틀린 강의 5강: 클래스와 객체 (0) | 2025.10.28 |
| 코틀린 강의 4강: 함수와 람다 표현식 (0) | 2025.10.28 |