본문 바로가기

코틀린

코틀린(Kotlin) - 타입 시스템 null 가능성 safe call(?.) elvis(?:)

코틀린에서 가장 중요한 부분인 타입 시스템(type system)을 살펴보도록 하겠습니다.

자바와 비교했을 때 코틀린의 타입 시스템은 코드의 가독성을 향상시키는 데 도움이 되는 몇 가지 특성이 있습니다.

그런 특성으로, null이 될 수 있는 타입(nullable type) 과 읽기 전용 컬렉션이 있습니다.

null 가능성

null 가능성은 NullPointerException(NPE) 을 피할 수 있는 코틀린 타입 시스템의 특성입니다.

NullPointerException 은 앱을 중지 시킬 정도로 굉장히 크리티컬 해서 사용자와 개발자 모두를 당황시키는 존재죠...😨😱

 

코틀린을 비롯한 최신 언어에서는 이런 null에 대한 이슈를 해결하기 위해, null이 될 수 있는 여부를 타입 시스템에 추가함으로써 런타임 단계가 아닌 컴파일 단계에서 오류를 미리 감지해서 예외를 줄일 수 있도록 하였습니다.

 

이러한 코틀린의 null이 될 수 있는 타입에 대해 알아보고, 이 값을 어떻게 표기하며 코틀린에서는 어떻게 처리하는지에 대해 살펴보도록 하겠습니다.

null이 될 수 있는 타입

코틀린과 자바의 가장 중요한 차이는 코틀린 타입 시스템이 null이 될 수 있는 타입을 명시적으로 지원한다는 점입니다.

이 말은 즉, null이 될 수 있는 타입은 프로퍼티나 변수가 null을 허용하게 만드는 방법입니다.

만약 자바의 경우 어떤 변수가 null이 될 수 있다면 NullPointerException이 발생할 수 있으므로 안전하지 않은 호출이라고 할 수 있습니다.

 

코틀린은 그런 호출을 금지함으로써 많은 오류를 방지하게끔 해줍니다! 👍

 

먼저 자바와 비교하기 위해 null이 될 수 있는 자바 코드를 살펴보겠습니다.

 

/* 자바 null이 될 수 있는 경우 */
int strLen(String s) {
    return s.length();
}

 

문제점이 한눈에 바로 보이실 겁니다.

네 맞습니다~ 이 함수에 파라미터로 null이 들어오게 되면 NullPointerException이 발생하게 됩니다.

 

이 함수를 코틀린으로 다시 작성해보겠습니다.

이런 함수를 작성할 때 가장 먼저 생각해야 하는 고민은 "이 함수가 null을 파라마티로 받을 수 있는가?" 입니다.

 

null이 들어올 수 없다면 다음과 같이 정의할 수 있습니다.

 

/* 코틀린 null을 허용하지 않는 파라미터 */
fun strLen(s: String) = s.length

 

위의 코틀린 strLen 함수에 null이거나 null이 될 수 있는 파라미터를 넘기는 것은 제한되며, 만약 그런 값을 넘기면 컴파일 시 오류가 발생합니다.

 

>>> strLen(null)

ERROR: Null can not be a value of a non-null type String

 

이 함수가 null값을 허용할 수 있게 하려면 타입 이름 뒤에 물음표(?)를 명시해주면 됩니다.

 

/* 코틀린 null을 허용하는 파라미터 */
fun strLenSafe(s: String?) = ...

 

String?, Int?, User? 등 어떠 타입이든 타입 이름 뒤에 물음표(?)를 붙이면 그 타입의 변수나 프로퍼티에 null을 허용할 수 있다는 뜻이 됩니다.

 

Type? = Type 또는 null 허용

 

반대로 물음표가 없는 타입은 그 변수가 null 참조를 할 수 없다는 뜻입니다.

따라서 모든 타입은 기본적으로 null이 될 수 없는 타입이고, 뒤에 ? 가 붙어야만 null이 될 수 있습니다. 

null이 될 수 있는 타입의 변수는 수행할 수 있는 연산이 제한되는데요, 예를 들어 null이 될 수 있는 변수에 대해 변수.메소드() 처럼 메소드를 직접 호출할 수는 없습니다.

 

/* null이 될 수 있는 변수에 대해 변수.메소드() 호출은 불가능 하다 */
>>> fun strLenSafe(s: String?) = s.length()

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

 

null이 될 수 있는 값을 null이 될 수 없는 변수에 대입하는 것도 불가능합니다.

 

/* null이 될 수 있는 값을 null이 될 수 없는 변수에 대입할 수 없다 */
val x: String? = null
var y: String = x

ERROR: Type mismatch: inferred type is String? but String was expected

 

안전한 호출(safe call) 연산자 "?."

코틀린이 제공하는 가장 유용한 기능 중 하나가 안전한 호출 연산자인 "?." 입니다.

"?."은  null 체크와 메소드 호출을 한 번의 연산으로 수행합니다. 

예를 들어 user?.getId() 는 if (user != null) { user.getId() } else { null } 과 동일합니다.

호출하려는 값이 null이 아니면 그대로 메소드가 호출되고, 호출하려는 값이 null이면 이 호출은 무시되면서 null값을 반환하게 됩니다.

 

 

그렇기 때문에 null 체크를 연달아해야 하는 경우도 코틀린에서는 훨씬 간결하게 null 체크를 할 수 있습니다.

 

/* null 체크가 연달아 필요한 경우 */
val country = user?.address?.country

엘비스 연산자 "?:"

코틀린은 null 대신 사용할 디폴트 값을 지정할 때 편리하게 사용할 수 있는 연산자를 제공합니다.

그 연산자는 엘비스 연산자 "?:" 입니다.

 

엘비스 프레슬리 헤어스타일과 눈

 

이 연산자는 좌항의 값이 null이 아니면 좌항 값을 그대로 반환하고, 만약 좌항 값이 null이면 우항 값을 디폴트 값으로 반환합니다.

 

/* 엘비스 연산자 사용 예제 */
fun getUserType(type: String?) {
    val userType: String = type ?: "guest" //type이 null이면 디폴트값은 guest 이다.
}    

 

null일 경우 디폴트값으로 바꿔준다.

 

엘비스 연산자를 안전한 호출 "?." 연산자와 함께 사용해서 객체가 null인 경우에 대비하여 사용하는 경우도 많습니다.

 

/* 엘비스 연산자를 활용해서 null값 다루기 */
val abTestType = abTestData?.type ?: defaultType

 

또한, 엘비스 연산자의 우항에는 return, throw 등의 결과도 넣을 수 있어서, 좌항의 값이 null이면 함수 리턴 값을 즉시 반환하거나 예외를 던지는 등의 프로그래밍이 가능합니다.

 

/* 엘비스 연산자에 return, throw 사용 예제 */
val userId = user?.getId() ?: return null

val userId = user?.getId() ?: throw IllegalArgumentException("No ID")