Develop!/Kotlin

안전한 호출과 엘비스 연산자 in kotlin

체리필터 2023. 9. 14. 09:44
728x90
반응형

Java를 쓰면서 가장 골치 아픈게 NPE 이다.

null인 객체를 바로 사용하려면 Runtime 시에 NPE가 발생하기 때문에 사용 전에 항상 if로 null인지 여부를 검증해 주거나 Optional을 사용해야 하기 때문이다. 코틀린에서는 이러한 골치아픈 내용을 손쉽게 사용할 수 있도록 해준다.

fun String.echo() {
    println(toUpperCase())
    println(this)
    println(toLowerCase())
}

fun safeOperation() {
    val s1: String? = "Howdy!"
    s1?.echo()

    val s2: String? = null
    s2?.echo()
}

이렇게 하기 위해 사용하는 것이 '?.' 이다. null이 될 수 있는 객체 뒤에 '.' 대신에 '?.'를 사용하면 알아서 null 일경우에는 실행되지 않도록 해 준다. 위의 소스 코드 예제를 보면 잘 알 수 있다.

s1의 경우에는 Howdy! 라는 문자가 들어가 있어서 안전하지만 s2는 null 이기 때문에 안전하지 않다. 하지만 '?.'를 사용하게 되면 다음과 같이 Error 없이 실행 되는 것을 볼 수 있다.

s2는 실행되지 않았음을 볼 수 있다.

's2?.echo'를 's2.echo'로 바꾸게 되면 다음과 같은 에러 메시지를 보여 준다.

?. 연산자를 사용하게 되면 얼마나 구문이 간단해 지는지 아래 코드를 통해 비교해 볼 수 있다.

fun checkLength(s: String?, expected: Int?) {
    val length1 = if (s != null) s.length else null
    val length2 = s?.length

    println("String: ${s}, expected: ${expected}")
    println("length1 length: ${length1}")
    println("length2 length: ${length2}")
}

fun safeCall() {
    checkLength("abc", 3)
    checkLength(null, null)
}

if 문 때문에 필요한 로직 (여기서는 s.length 밖에 안되지만...)이 눈에 잘 안 들어 온다. 하지만 length2 의 경우에는 필요한 내용이 바로 읽히게 된다.

큰 의미 없지만... 실행 결과는 아래와 같다.

다음으로 볼 것은 엘비스(Elvis) 연산자이다. 사용법은 '?:' 를 사용하면 된다.

Java의 Map에서 값을 가져올 때 사용하는 'getOrDefault(Object key, V DefaultValue)'와 비슷하다. 값이 있을 경우에는 그대로 사용하고 없을 경우에는 '?:' 오른 쪽에 있는 값을 사용하게 된다.

fun elvis() {
    val s1: String? = "abc"
    println(s1 ?: "---")

    val s2: String? = null
    println(s2 ?: "---")
}

s2는 null 이므로 '---'를 출력하게 된다.

엘비스 연산자는 위에서 checkLength 함수에서처럼 null일 경우 실행조차 안되는 것보다 기본값을 반환하는 것이 좋을 때 사용한다.

fun checkLength(s: String?, expected: Int?) {
    val length1 = if (s != null) s.length else 0
    val length2 = s?.length ?: 0

    println("String: ${s}, expected: ${expected}")
    println("length1 length: ${length1}")
    println("length2 length: ${length2}")
}

안전한 호출과 엘비스 연산자를 통해 여러 Depth의 객체를 호출할 때 계속되는 null check를 안해도 되기 때문에 다음 처럼 편하게 사용할 수 있다.

fun chainedCall() {
    class Person(val name: String, var friend: Person? = null)

    val alice = Person("Alice")
    println(alice.friend?.friend?.name)

    val bob = Person("Bob")
    val charlie = Person("Charlie", bob)
    bob.friend = charlie
    println(bob.friend?.friend?.name)

    println(alice.friend?.friend?.name ?: "Unknown")
}

alice의 친구가 없어도 마음 놓고 호출할 수 있다.

charlie의 친구가 bob이고 bob의 친구가 charlie가 된 경우처럼 값이 모두 있을 경우에는 당연히 호출이 가능하다.

엘비스 호출자를 사용하면 null 대신에 Unknow 처럼 기본 값을 출력할 수 있다.

 

728x90
반응형

'Develop! > Kotlin' 카테고리의 다른 글

람다 in Kotlin  (0) 2023.09.25
제네릭스 in Kotlin  (0) 2023.09.22
Null이 될 수 없는 타입 in Kotlin  (0) 2023.09.06
구조 분해 선언 in Kotlin  (0) 2023.09.06
Data Class in Kotlin  (0) 2023.09.04