null safety through type conversion (null 안정성을 위한 형변환)

null 안정성을 위한 형변환

  • null 값이 담긴 객체에 대해 보다 안정한 형 변환 방법을 제공하고 있다.
  • null 을 허용하는 변수가 null 값이 들어있지 않다는것을 보장해주면, null 을 허용하지 않는 타입으로 스마트 캐스팅이 발생한다.

null 을 허용하는 타입

 null 을 허용하는 타입으로 선언했을 경우, 해당 타입에서 메서드를 호출하게 될 경우 아래와 같은 에러가 발생한다.

fun testMethod1(str: String?) {
println(str.length)
}

Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?


 str 이 null 일 경우, length 를 구할 수 없으므로 이를 safe call 연산자(?.) 을 사용하던지, non-null asserted (!!.) 를 사용하여 null 허용 타입으로 부터 예외처리를 해야 한다.

Safe Call (?.):

Safe call은 Kotlin에서 null 안정성을 보장하기 위한 기능 중 하나로, 변수가 nullable하면서 null일 수 있는 경우에만 사용됩니다. Safe call 연산자인 ?.을 사용하면, 해당 변수가 null인 경우에도 프로그램이 중단되지 않고 null을 반환합니다. 즉, 호출된 메서드나 프로퍼티가 null인 경우, 그냥 null을 반환하고 뒤따라오는 연산은 수행되지 않습니다.

fun main() {
testMethod1("안녕하세요")
testMethod1(null)
}

fun testMethod1(str: String?) {
println(str?.length)
}

결과값 >
5
null

testMethod1(null): 두 번째 호출은 null을 전달하고 있습니다. 이 경우 str은 nullable이며 null을 가지고 있기 때문에, safe call을 사용하여 null에 접근하더라도 NullPointerException이 발생하지 않습니다. str?.length 표현식은 null을 출력하게 됩니다.

위 예제에서 사용된 str?.length은 null 안정성을 보장하기 위해 중요한 역할을 합니다. 만약에 str.length와 같이 safe call 연산자를 사용하지 않는다면, str이 null일 경우에 NullPointerException이 발생하게 될 것입니다. 하지만 str?.length와 같이 safe call 연산자를 사용함으로써, null을 안전하게 다루고 프로그램이 예외 없이 실행될 수 있습니다.

Non-null Assertion (!!., Non-null asserted call):

NonNull Assertion은 Kotlin에서 변수의 null 여부와 상관없이 해당 변수를 non-null 타입으로 간주하도록 하는 기능입니다. 이는 명시적으로 해당 변수가 null이 아니라고 단언하는 것으로 사용됩니다. Non-null asserted call 연산자인 !!.을 사용하면, 변수가 null이면 NullPointerException이 발생할 수 있으므로 주의해야 합니다. 사용 시에는 null이 아닌 확실한 상황에서만 사용하는 것이 좋습니다.

fun main() {
testMethod1("안녕하세요")
testMethod1(null)
}

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

5
Exception in thread "main" java.lang.NullPointerException
at MainKt.testMethod1(Main.kt:7)
at MainKt.main(Main.kt:3)
at MainKt.main(Main.kt)


testMethod1(null): 두 번째 호출은 null을 전달하고 있습니다. 이 경우 str은 nullable이며 null을 가지고 있기 때문에 Non-null Assertion(!!.)을 사용하여 강제로 non-null 타입으로 캐스팅하려고 합니다. 하지만 str이 null이기 때문에 Non-null Assertion(!!.) 연산자를 사용할 때 NullPointerException이 발생합니다. Non-null Assertion은 변수가 null이 아님을 개발자가 명시적으로 보장할 때만 사용해야 합니다. 그러나 이 경우에는 str이 null인 상태에서 non-null로 강제 변환하려 하기 때문에 예외가 발생합니다.

if 문 사용

  • if 문을 통해 null 허용 변수에 null 값이 아닌 객체의 주소 값이 들어 있음을 검사해주면, if 문 내부에서는 null을 허용하지 않는 변수로 변환되어 사용할 수 있다.
  • if 문이 종료되면 다시 null 허용 변수가 된다. 
  • 이 때, 비교 연산자 보다는 is 연산자를 추천한다.
  • 비교 연산자로 검사할 경우 타입이 Any 인 경우 컴파일 오류가 발생한다.
fun main() {
testMethod1("안녕하세요")
testMethod1(null)
}

fun testMethod1(str: String?) {
println(str?.length)

if ( str is String) {
// 스마트 캐스팅
println(str.length)
}
}
if 문 외부에서, str.length 로 접근 시, 빌드에러가 발생하지만, if 문 내부에서 is 연산자로 null 이 아님을 검사해주게되면, if 문 내에서는 스마트 캐스팅이 발생하여, str이 if 문 내에서 잠깐 null 허용 변수가 아니게 되므로 str.length 로 접근 시 에러가 발생하지 않게됩니다.

if 블록 내에서 스마트 캐스트 기능이 활용됩니다. 컴파일러는 이 블록 내에서 str이 String 타입임을 알기 때문에 안전한 호출 연산자(?.)를 사용하지 않고도 해당 속성과 함수에 접근할 수 있게 됩니다. 따라서 str.length를 안전하게 호출할 수 있습니다.

if 문이 종료되면 다시 null 허용 변수가 되어서, str.length 접근시 에러가 발생한다.

fun testMethod1(str: String?) {
println(str?.length)

if ( str is String) {
// 스마트 캐스팅
println(str.length)
}

if ( str != null ) {
// 스마트 캐스팅
println(str.length)
}
}

!= null 조건문으로 null 안정성을 보장해도 동일한 스마트캐스팅이 발생되어 동일하게 사용할 수 있다.

비교 연산자 vs is 연산자

fun main() {
testMethod2("안녕하세요")
testMethod2(null)
}

fun testMethod2(str: Any?) {
if ( str is String) {
// 스마트 캐스팅
println(str.length)
}

if ( str != null ) {
println(str.length) // 오류가 발생한다.
}
}
Kotlin: Unresolved reference: length

이번에는 testMethod의 인자가 Any? 타입일 때의 상황입니다. null을 허용하는 Any 타입으로 함수의 인자를 정의해두고, null 이 넘어 왔을 때, 두 번째 조건문에서는 오류가 발생한다.

해당 변수가 null 이 아님을 보장 할 수 있으나, String 타입인것은 보장 할 수 없기 때문에, length 메서드를 찾을 수 없기 때문에 오류가 발생한다.

따라서, null 스마트 캐스팅을 이용할 때는 != null 구문 보다는 is String 을 통한 is 연산자로 처리하는것이 더 좋은 코딩 습관이다.

댓글

이 블로그의 인기 게시물

Intel® HAXM installation failed 해결하기

Kotlin Interface

Kotlin this, super