Result<T>는 성공이면 값을, 실패면 예외를 담는 래퍼(wrapper) 클래스. Result는 다음 두 가지 상태 중 하나를 가짐.try-catch보다 깔끔한 코드public inline fun <R> runCatching(block: () -> R): Result<R>
Result로 감싸주는 유틸리티 함수try-catch를 수행하고 결과를 Result.Success 또는 Result.Failure로 반환inline fun <R> runCatching(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (e: Throwable) {
Result.failure(e)
}
}
val result = runCatching { "abc".toInt() }
result.onSuccess {
println("변환 성공: $it")
}.onFailure {
println("에러 발생: ${it.message}")
}
try-catch보다 간결하고 함수형 체이닝이 가능Result로 감싸면 멤버 함수를 활용하여 쉽게 처리 가능Throwable을 상속한 예외만 감지
return, break, exitProcess() 등은 예외가 아니기에 Result에 담기지 않음비동기 환경에서는 예외 전파 방식에 주의 필요.
suspend 함수는 runCatching 안에서 예외 감지 가능. suspend 함수가 예외를 던지면 runCatching 안의 try-catch에 의해 잡힘launch는 예외를 비동기적으로 처리하기에 runCatching에서 감지 불가능. launch는 코루틴을 별도 작업으로 실행해서 내부에서 예외가 발생해도, launch 블록 자체에서 비동기적으로 처리. runCatching 블록이 끝날 때까지 예외가 전파되지 않음.launch는 예외를 CoroutineExceptionHandler로 전달하거나 상위 스코프를 취소. runCatching은 launch 자체가 반환하는 Job을 감쌀 뿐, Job 내부의 예외는 감지 불가async { ... }.await()는 await() 시점에 예외가 발생하므로 감지 가능. 또한 launch 내부에서 runCatching사용해야 됨예제코드
//1. 예외 감지
val result = runCatching {
throw IllegalArgumentException("잘못된 인자")
}
println(result.isFailure) // true
//2. 예외 아닌 것은 감지 불가능
fun test(): Result<Int> {
return runCatching {
return 100 // 함수 자체가 여기서 종료. 예외가 아니기에 감지 안 됨
}
}
println(test()) // Result.success(100)이 나오지만 실제로 runCatching 블록이 중간에 종료됨
val result = runCatching {
exitProcess(0) // 프로그램 종료. 예외 아니기에 catch 안 됨
}
println("출력되지 않음")
//3. suspend
suspend fun mayFail(): Int {
throw RuntimeException("실패")
}
val result = runCatching { mayFail() } // 정상적으로 Result.failure로 감지됨
//4. launch 예외 감지 불가능
runBlocking {
val result = runCatching {
val job = launch {
throw IllegalStateException("예외 발생")
}
job.join()
}
println(result.isSuccess) // true, 예외는 잡히지 않음
}
//5. async+await
val result = runCatching {
val deferred = async {
throw RuntimeException("예외")
}
deferred.await() // 여기서 예외 발생하기에 runCatching 감지 가능
}
//6. launch 예외 감지 방법
launch {
val result = runCatching {
throw RuntimeException("코루틴 내부 예외")
}
if (result.isFailure) {
println("잡았다")
}
}
val isSuccess: Boolean
val isFailure: Boolean
fun exceptionOrNull(): Throwable?
inline fun getOrNull(): T?
open override fun toString(): String
inline fun <R, T : R> Result<T>.getOrDefault(defaultValue: R): R
inline fun <T> Result<T>.getOrThrow(): T
inline fun <R, T : R> Result<T>.getOrElse(onFailure: (exception: Throwable) -> R): R
inline fun <R, T : R> Result<T>
.recover(transform: (exception: Throwable) -> R): Result<R>
inline fun <R, T : R> Result<T>.recoverCatching(
transform: (exception: Throwable) -> R
): Result<R>
inline fun <T> Result<T>.onFailure(action: (exception: Throwable) -> Unit): Result<T>
inline fun <R, T> Result<T>.map(transform: (value: T) -> R): Result<R>
inline fun <R, T> Result<T>.mapCatching(transform: (value: T) -> R): Result<R>
inline fun <T> Result<T>.onSuccess(action: (value: T) -> Unit): Result<T>
inline fun <R, T> Result<T>.fold(
onSuccess: (value: T) -> R,
onFailure: (exception: Throwable) -> R
): R
fun main() {
// 1. isSuccess, isFailure
val resultSuccess1 = runCatching { "123".toInt() }
println("resultSuccess1 isSuccess=${resultSuccess1.isSuccess},"+
"isFailure=${resultSuccess1.isFailure}")
// resultSuccess1 isSuccess=true, isFailure=false
val resultFailure1 = runCatching { "abc".toInt() }
println("resultFailure1 isSuccess=${resultFailure1.isSuccess},"+
"isFailure=${resultFailure1.isFailure}")
// resultFailure1 isSuccess=false, isFailure=true
// 2. exceptionOrNull
val exception2 = resultFailure1.exceptionOrNull()
println("exceptionOrNull=${exception2?.message}")
// exceptionOrNull=For input string: "abc"
// 3. getOrNull
val value3 = resultSuccess1.getOrNull()
println("getOrNull=$value3")
// getOrNull=123
// 4. toString
println("resultSuccess1.toString()=$resultSuccess1")
// resultSuccess1.toString()=Success(123)
println("resultFailure1.toString()=$resultFailure1")
// resultFailure1.toString()=
// Failure(java.lang.NumberFormatException: For input string: "abc")
// 5. getOrDefault
val value5 = resultFailure1.getOrDefault(0)
println("getOrDefault=$value5")
// getOrDefault=0
// 6. getOrThrow (success case)
val value6 = resultSuccess1.getOrThrow()
println("getOrThrow (success)=$value6")
// getOrThrow (success)=123
// 7. getOrElse
val value7 = resultFailure1.getOrElse { ex ->
println("getOrElse onFailure message=${ex.message}")
-1
}
// getOrElse onFailure message=For input string: "abc"
println("getOrElse result=$value7")
// getOrElse result=-1
// 8. recover
val resultRecover8 = resultFailure1.recover { ex ->
println("recover transform message=${ex.message}")
0
}
// recover transform message=For input string: "abc"
println("recover result=$resultRecover8")
// recover result=Success(0)
// 9. recoverCatching
val resultRecoverCatch9 = resultFailure1.recoverCatching {
throw IllegalStateException("recover failed")
}
println("recoverCatching result=$resultRecoverCatch9")
// recoverCatching result=
//Failure(java.lang.IllegalStateException: recover failed)
// 10. onFailure
val resultOnFailure10 = resultFailure1.onFailure { ex ->
println("onFailure message=${ex.message}")
}
// onFailure message=For input string: "abc"
println("onFailure result=$resultOnFailure10")
// onFailure result=
//Failure(java.lang.NumberFormatException: For input string: "abc")
// 11. onSuccess
val resultOnSuccess11 = resultSuccess1.onSuccess { v ->
println("onSuccess value=$v")
}
// onSuccess value=123
println("onSuccess result=$resultOnSuccess11")
// onSuccess result=Success(123)
// 12. map
val resultMap12 = resultSuccess1.map { it * 2 }
println("map result=$resultMap12")
// map result=Success(246)
// 13. mapCatching
val resultMapCatch13 = resultSuccess1.mapCatching { v ->
if (v > 100) throw IllegalStateException("over 100")
v * 2
}
println("mapCatching result=$resultMapCatch13")
// mapCatching result=Failure(java.lang.IllegalStateException: over 100)
// 14. fold
val message14 = resultFailure1.fold(
onSuccess = { "fold success: $it" },
onFailure = { "fold failure: ${it.message}" }
)
println(message14)
// fold failure: For input string: "abc"
}