[코클린 기초] 코틀린 널 안정성(Null Safety)
Null 참조의 위험성
자바를 포함한 많은 프로그래밍 언어에서 가장 많이 발생하는 예외가 바로 NullPointerException일 것입니다. 줄여서 NPE라고도 합니다. Null을 발명한 토니호어는 1965년 null을 발명한 것이 자신의 1조원짜리 실수였다고 고백합니다. 왜냐하면 null로 인해서 발생한 오류와 피해가 수십년간 수십억 달러에 달하기 때문이라고 합니다.
자바에서는 이러한 Null로 인해 발생하는 NPE를 줄이기 위해 Optional 이라는 클래스 타입을 제공하기 시작했습니다. 하지만 자바에서 아무리 Optional을 사용한다고 하더라도 Optional은 타겟값을 감싸는 클래스이기 때문에 그에 대한 오버헤드가 발생하고 컴파일을 하는 단계에서는 문법적 요소만 통과한다면 Null 가능성에 대해서까지는 깊게 검사할 수 없습니다. 이는 런타임에 발생하고 결국 테스트 코드를 통해 런타임을 돌려봐야만 알 수 있습니다.(심지어 테스트코드가 없다면... 운영단계에서 고객의 목소리로 듣게 되겠네요..ㅠㅠ)
코틀린을 비롯한 최신 언어에서는 컴파일러가 이러한 Null 참조 가능성에 대한 부분까지 검사해주기 때문에 NPE 가능성에 대해서 기존 언어에 대비하여 많이 줄일 수 있다고 합니다.
Kotlin에서는 문법적으로 Null을 허용하는 타입과 Null이 허용되지 않는 타입이 구분되어 있습니다. 코드로 확인해보겠습니다.
val a: String = null // 컴파일 에러
var b: String = "aabbcc"
b = null // 컴파일 에러
여기서 변수 a와 b는 모두 null을 허용하지 않는 변수입니다. 그럼 null을 허용하는 변수는 어떻게 선언할 수 있을까요?
val a: String? = null
var b: String? = "aabbcc"
b = null
코틀린에서는 물음표(?) 키워드가 많이 쓰입니다. 여기서의 물음표 키워드는 String 값이 null일수도 아닐수도 있다는 의미입니다. 즉, null을 허용한다는 의미입니다.
그럼 다음 코드는 코틀린 컴파일러가 어떻게 해석할 수 있을까요??
println(a.length)
a 변수의 문자열의 길이를 출력하는 구문입니다. 애석하지만 해당 코드는 실행할 수 없습니다. 엄밀히 말하면 컴파일할 수 없습니다.
왜냐하면 변수 a는 이미 null을 허용하는 타입의 String 이기 때문입니다. 따라서 코틀린은 이에 대한 물음표 키워드를 제공합니다.
println(a?.length)
해당 코드는 a 변수가 null이면 null을 리턴하고 값이 있으면 length를 리턴합니다. 일종의 안전 연산자입니다.
엘비스 연산자(?:)
이러한 ? 표현식이 사용되는 구문은 엘비스 연산자라는 구문에도 사용됩니다. 엘비스 연산자는 아래와 같이 사용됩니다.
val c = a?.length ?: 0
위 코드는 a?.length 값이 null이 리턴된다면 0을 출력하고 a?.length 값이 존재한다면 해당 값을 출력합니다. 자바에서의 삼항연산자와 유사한 문법입니다. 어쩌면 default 값을 정하기에 좋을 것 같아보입니다.
코틀린에서 NPE를 조심해야하는 경우
우선 코틀린에서 NPE가 발생하는 경우는 자바에서보다 적을 가능성이 높다고 합니다.(가능성이 높습니다. 어플리케이션 기준으로는 같은 언어를 쓰더라도 개발자 역량에 따라 다릅니다.) 하지만 가능성이 자바가 더 높기 때문에 같은 JVM 메모리 시스템을 공유하는 JAVA와 Kotlin은 서로 공유될 수 있는데요.
예를 들어, Java를 Jar화하여 kotlin 프로젝트에서 활용하는 경우입니다. 이 경우, 자바코드에서의 NPE는 자바코드를 어떻게 작성했는지에 따라서 동작이 달라지기 때문에 코틀린의 영역이 아니므로 컴파일러가 자바코드의 NPE검사를 진행할 수 없습니다.
따라서 자바 클래스와 같이 사용하는 경우에는 NPE 발생 가능성에 대해서 고려하여 코드를 작성해야 합니다. (안전 연산자 사용 권장!!)