Kotlin으로 코드를 작성하다보면 보게되는 연산자들이 있습니다.

?. , ?: , !! 모두 Null 타입을 처리하기위해 사용하는 연산자들입니다.

Java와 비교하면서 천천히 알아보겠습니다.

In Java
private void doSomething(String str) {
    System.out.println(str.length());
}

위의 코드는 안전한 코드일까요?
.
.
.
아닙니다.

str 이 null 일 가능성이 있기 때문입니다. null에 대해 length()를 호출하면 NullPointerException이 발생하죠.

private void doSomething(String str) {
    if (str != null) {
        System.out.println(str.length());
    }
}

이렇게 써야 안전한 코드라고 할 수 있겠습니다.

In Kotlin

Kotlin은 어떨까요?

private fun doSomething(str: String) {
    println(str.length)
}

위의 코드는 안전한 코드일까요?
.
.
.
맞습니다.

왜일까요?
Kotlin은 null 이 가능한 타입과 불가능한 타입을 확실히 구분합니다.

String 타입은 절대 null이 될 수 없는 타입입니다.
String? 타입이 nullable 타입입니다.

따라서 str은 String 타입이므로 null check를 안해도 됩니다.

private fun doSomething(str: String?) {
    if (str != null) {
        println(str.length)
    }
}

String? 같은 nullable 타입일 때만 null check를 해주면 됩니다.
하지만 코틀린에서 제공해주는 ?. , ?: , !! 를 사용하면 더 섹시하게 처리할 수 있습니다.

?. Safe call operator
private fun doSomething(str: String?) {
    val length = str?.length  
    println(length)
    // println(str?.length)
}

str?.lengthlength 변수에 할당해서 사용했습니다.
?.는 safe call operator라고 합니다.

str.length 를 호출 하면 str이 null일 수 도 있기 때문에 NPE가 발생할 확률이 있습니다.
(Kotlin에선 컴파일 오류)

애초에 NPE가 왜 발생할까요?
우선 length() 는 String 클래스 안에 정의된 함수(메소드)입니다.

str변수에 String 인스턴스가 할당돼있지 않은 상태에서 String 클래스의 함수를 호출하기 때문입니다.
null.length() 를 호출하는 꼴입니다. null에는 당연히 length() 라는 함수가 없습니다.
그래서 NPE가 발생합니다.

safe call operator를 사용해서 str?.length 호출하게되면 str이 null일 경우 뒤의 호출을 무시하고 식의 전체 결과가 null로 반환됩니다.

val doubleLength = str?.length?.toDouble()이렇게 chaining하여 사용할 수도 있습니다.

또, Kotlin은 Scope function 이라는 걸 제공합니다.
https://kotlinlang.org/docs/scope-functions.html

private fun doSomething(str: String?) {
    str?.let { notNullStr ->
        println(notNullStr)
    }
}

let 을 사용해서 null 안정성을 확보해도 됩니다.

질문: str?.length의 타입은 무엇일까요?

?: Elvis operator

세워서 보면 엘비스 프레슬리를 닮아서 이름 지어진 연산자입니다.

?: 널이라면? 으로 해석하시면 편할 거 같습니다.

private fun doSomething(str: String?) {
    val str2 = str ?: "Hello"
    println(length)
}

str 이 널이라면 str2"Hello" 를 할당한다. 라는 뜻입니다.

자바로 보면 대충 이렇겠죠.

String str2 = "";
if (str == null) {
    str2 = "Hello";
} else {
    str2 = str;
}

물론 safe call과 함께 이용할 수도 있습니다.
val doubleLength = str?.length?.toDouble() ?: 0.0

값 대신 함수를 종료하거나 예외를 던질 수도 있습니다.
val str2 = str ?: return
val str2 = str ?: throw Exception()

!! Not-null assertion operator

얘는 절대 null 아님으로 해석하시면 될 거 같습니다.

private fun doSomething(str: String?) {
    println(str!!.length)
}

이렇게 하면 컴파일 오류 없이 length 를 호출할 수 있습니다.
하지만 런타임에서 NPE가 터질 확률이 있죠. 그래서 웬만하면 사용하지 말라는 연산자이고,
공식문서에도 for NPE-lovers라는 표현이 나옵니다.

여러분은 어떻게 생각하시나요? !! 은 무조건 쓰면 안되는 걸까요?

+ Recent posts