CS/Kotlin

[Kotlin 문법] 9. Null Safety

졔졔311 2024. 6. 13. 15:40
728x90
반응형

kotlin에서는 null값을 사용하기 때문에, 이로 인해 문제가 발생할 수 있다.

따라서 null safety가 존재한다. (주로 compile-time에 이용)

즉, null safety는 null값에 대한 안전한 대응을 할 수 있도록 만들어주는 방식인 것이다.

 

Nullable type

원래 어떤 type이든 null을 허용하지 않지만, null값을 가질 수 있도록 허용해준 nullable type이 존재한다.

이 type은 type 마지막에 '?'를 붙여 선언할 수 있다.

예를 들어,

var neverNull: String = "This can't be null"
neverNull = null	// compile error

위와 같이 neverNull이라는 String 변수에 null을 대입하면, 

Null cannot be a value of a non-null type 'kotlin.String'.

라는 error 문구와 함께 compile error가 발생한다.

 

반면에,

var nullable: String? = "You can keep a null here"
nullable = null

위와 같이 String이라는 type에 '?'를 붙여 선언하면, nullable이라는 변수에 null을 대입해도 정상 실행된다.

 

safe call

객체의 property나 memeber function에 접근할 때 null일 가능성이 있다면, safe call operator인 '?.'를 사용할 수 있다.

fun lengthFunc(str: String?): Int? = str?.length

이렇게 nullable한 String 변수 str을 매개변수로 받아, length를 리턴하는 함수를 선언했다고 하자.

원래라면 str.length를 리턴할 것이고, 따라서 str이 null이라면 compile error가 발생할 것이다.

하지만 여기서 safe call operator를 사용해 'str?.length'라고 표현하였기 때문에,

str이 실제 null이더라도 error가 발생하지 않고 null을 리턴하게 된다.

a.p1?.p2?.p3?.p4 이런식으로 연쇄적으로 사용하는 것도 가능한데, 이 경우에도 단 하나의 property라도 null인 경우가 발생하면 null을 리턴하게 된다.

 

Elvis operator

Elvis operator는 '?:'로 표시하는데, 이걸 사용해 null 값을 대신할 default value를 리턴하게 해주는 기능을 한다.

Elvis operator의 좌측에는 null인지 체크할 대상, 우측에는 null일 경우 리턴할 값을 쓰면 된다.

fun main() {
    val str: String? = null
    println(str?.length ?: 0)	// print 0
}

위의 상황에서, str 변수는 null 값을 가지고 있다.

null일 경우 0을 출력하고 싶으므로,

'?:'를 기준으로

왼쪽에는 str?.length라는 체크 대상,

오른쪽에는 0이라는 null 대체 출력 대상을 둔다.

이렇게 하면 null이어도 0이 출력되는 것을 확인할 수 있다.

 

Double Bang(Not-Null Assertion Operator)

이 operator는 '!!'로 표시하는데, nullable 값을 강제로 non-null로 변환하는 연산자이다.

예를 들어, 다음과 같은 코드를 보자.

fun main() {
    val s1 = readLine()!! // 입력값이 null이면 NPE 발생
    println(s1)
}

위 코드에서 readLine()은 nullable 타입(String?)을 반환하는데, '!!'를 붙임으로써 이 값이 절대 null이 아니라고 보장하는 것이다.

만약 null이 들어오면, NullPointerException(NPE)이 발생하게 된다.

 

'!!' 없이 안전한 코드를 만드는 것을 추천하는데,

그 방법은 다음과 같이 크게 두 가지가 있다.

 

먼저, 엘비스 연산자(?:)를 사용한 기본값 설정 방식이다.

fun main() {
    val s1 = readLine() ?: "기본값"
    println(s1)
}

이에 대해서는 위에서 설명했으니, 생략하겠다.

 

다음으로, '?.let'을 사용한 null 체크 방식이다.

'?.let'은 null-safe 호출을 위한 함수로, null이 아닐 때만 특정 로직을 실행하도록 도와준다.

val name: String? = "Kotlin"
name?.let {
    println("이름: $it")  // name이 null이 아니면 실행됨
}

위 코드처럼, null이 아닐 때만 {} 블록이 실행되도록 하기 위해 사용한다.

이걸 readLine을 사용한 코드에 접목하면, 다음과 같다.

fun main() {
    val s1 = readLine()?.let { it } ?: "기본값"
    println(s1)
}

 

여기서 it은 ?.let 내부에서 원래 변수 값을 가리킨다.

null이 아니면 let 내부의 {} 블록을 실행하므로, 읽어온 값을 반환해 s1에 넣어주고,

null이면 "기본값"을 s1에 반환하게 된다.

728x90
반응형