코틀린의 Null Safety 이해하기

isTuna·2022년 4월 3일
5

Kotlin

목록 보기
1/1
post-thumbnail

Null이 왜?

동적 언어로 오랜 기간 개발을 해온 저는 Null이 가져올 수 있는 후폭풍을 깊게 고민해본 적이 없었던 것 같습니다. Python에서의 None, Ruby에서의 nil은 큰 문제라기보다는, "비어있구나.." 또는 "아, nil이 올수도 있네 대응해야겠다." 하고 주제 넘게 가볍게 여겼습니다.

하지만 코틀린은 기본적으로 Null이 변수에 담길 수 없게 제한하고 있을 정도로 Null에 대해 심각하게 다룹니다. Java를 기반으로 둔 코틀린은 Java에서 문제로 여겨졌던 NPE(NullPointerException)을 대응하고 나온 언어입니다.

Null에 대한 여담

Null 레퍼런스가 Billion Dollar Mistake라고 표현하는 경우도 많습니다. 1965년도에 처음으로 공개된 Null은 퀵소트를 창시한 Tony Hoare에 의해서 만들어졌는데, 당시에는 단순하게 구현하기 편하기 위해서 만들어졌습니다. 하지만 현대에 와서는 사람들이 역사적으로 잘못된 발명이라고들 합니다.

코틀린에서의 Null

앞서 말했듯이 코틀린에서는 기본적으로 Null이 변수에 담기지 못하게 제한한다고 했습니다.
물론 담게 하는 방법이 존재하지만 코틀린은 그만큼 NPE와 치열한 싸움을 하고 있습니다. ⚔️

null을 변수에 담으려고하면 아래와 같은 현상이 발생합니다.


val catName: String = null
// Null can not be a value of a non-null type String

Intellij IDE에서 Nullnon-null 타입의 String에 할당할 수 없다고 말해주며 빨간줄이 생깁니다.

하지만 Null이 필요한 경우는 자주 생깁니다.그럴 경우에는 (?)를 붙여줘서 Null을 담을 수 있게 하면 됩니다.

val catName: String? = null
println(catName) //null

아주 쉽죠!! 😎

코틀린이 Null을 제한한다고 하긴 하지만 생각보다 쉽게 할당할 수 있습니다.

그치만 Null이 할당된 변수를 다룰 때는 기존과는 다른 연산자들을 사용해야합니다. 기초적인 + / * 와 같은 사칙연산자들도 Nullable한 변수에는 사용할 수 없습니다.

val cats: Int? = 1

println(cats + 1)
//Operator call corresponds to a dot-qualified call 'cats.plus(1)' which is not allowed on a nullable receiver 'cats'.

(?)을 타입 이름 뒤에 붙여줌으로써 Nullable referance가 된 변수에는 뭐가 담겨져 있을지 모르기 때문에 연산을 하지 못하게 제한합니다.

코틀린은 Null referance로 인해서 발생할 수 있는 문제들을 원천봉쇄하기 위해 타입 시스템을 설계한 언어입니다.

그래도 너무 많이 제한하는게 아닌가 싶겠지만 Null을 안전하게 사용하기 위해 Safe Call이라는 개념이 등장합니다.

Safe Call

Nullable 한 변수에 접근하기 위한 방법으로 safe call operator?.을 사용할 수 있습니다.


앞서 선언했던 예시에 한번 대입해보겠습니다.

val catName: String? = null

println(catName.length)
//Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

println(catName?.length) 
//null

문자열의 길이를 가진 프로퍼티인 length를 불러오면 safe or non-null asserted calls에만 허용한다고 제한합니다.

그래서 catName뒤에 ?를 붙여줘서 safe call로 만들어주면 null이 출력되는 것을 확인 할 수 있습니다.

Safe call을 non-nullable한 변수에도 붙여줄 수 있지만 불필요한 Safe call이라고 말해줍니다.


val catName: String = "Fluffy"

println(catName?.length)
//Unnecessary safe call on a non-null receiver of type String. 
//output => 6

Unnecessary safe call on a non-null receiver라는 문구가 뜨면서 IDE에서 dot call로 변환하길 추천합니다.

Safe call 사칙연산

위에서 실패했던 사칙연산을 다시 봅시다.

	println(cats?  + 1)
    //온갖 에러

위와 같은 형태는 당연히 오류가 납니다. 😞

Nullable한 변수로 연산을 할때는 .plus(), .minus(), .rem()와 같은 메소드들을 사용해야합니다.

println(cats?.plus(1))
// 2
println(cats?.times(8))
// 8

Elvis Operator

코틀린에서 Null safety를 말하면 Elvis operator를 빼놓을 수 없습니다.

Elvis Operator는 ?:로 생김새가 록앤롤의 제왕 Elvis presley를 닮아 붙여진 이름입니다.

Null을 다루다보면 꼭 나오면 상황이 있습니다.

x 값이 Null일 때는 0을 저장하고 아니면 x값을 저장하게 하자!

이런 상황에서 if문으로 null인지 체크하고 분기를 태움으로 문제를 해결 할 수 있습니다. 삼항연산자

하지만 코틀린에서는 이것을 간소화할 수 있습니다.

val dogName: String? = "Sulgi"

val nameLength: Int = if (dogName! = null) dogName.length else 0

----------------

val nameLength: Int = dogName?.length?:0

위의 두 코드는 같은 일을 하지만 Elvis operator를 사용했을 때 쉽고 명확하게 이해할 수 있습니다.

Bang Bang Operator

이름이 Bang Bang이라는 연산자가 있다니...

Bang Bang Operator (!!)는 NPE를 사랑하는 사람들을 위해 만들어진 연산자입니다.

NPE를 코틀린이 아닌 우리가 다루겠다면 사용할 것을 권하는 연산자라고 말하는 사람들도 있습니다.

val length = dogName!!.length

위와 같이 Nullable한 변수에 (!!)을 붙이게 되면 변수에 들어 있는게 어떤 값이든 non-null 타입으로 변환해줍니다.

하지만 non-null 타입으로 변환하려는 변수가 null이면 NPE가 발생하게 되는거죠.😰

Safe Calls for Data class

Data class에서 Safe call이 어떻게 사용되는지 예제로 보겠습니다. Data class 두개를 정의 해봅시다.

data class Dog(val species: Species?)

data class Species(val name: String?)

speciesname 필드는 nullable한 타입들입니다. 두 필드를 안전하게 접근하기 위해서는 Safe call을 이용할 수 있습니다.


val dog: Dog? = Dog(Species("Maltese"))

val result = dog?.species?.name

assertEquals(result,"Maltese")

만약 dogspeciesnull을 가지고 있다면,


val dog: Dog? = Dog(Species(null))

val result = dog?.species?.name

assertNull(result)

위와 같이 사용할 수 있습니다.

profile
청소연구소 개발자 (2021. 05~ )

0개의 댓글