코틀린 Null safety

김성준·2022년 4월 22일
0

Kotlin

목록 보기
6/17

Nullable types and non-null types

코틀린의 타입 시스텀은 널을 참조할 수 있는 가능성을 제거하는데 초점이 맞춰져 있습니다. 많은 프로그래밍 언어들을 사용하는데 가장 흔히 발생하는 에러중에 하나는 NullPointerException 입니다.

코틀린에서 NullPointerException이 발생하는 경우는 아래와 같습니다.

  • 명시적으로 'throw NullPointerException()'을 호출 할 때.

  • null을 참조하는 변수에 !! 연산자를 사용해서 접근할 때.

  • 생성자에서 초기화 되지 않은 this가 인자로 들어와서 어딘가에서 사용될 때.

  • 슈퍼클래스 생성자가 구현 되지 않은 파생 클래스의 멤버를 호출할 때.

코틀린에서 타입 시스템은 null을 가질 수 있는 참조와 가질 수 없는 참조를 구분합니다. 예를 들어, 일반적인 String 타입의 변수는 null을 가질 수 없습니다.

var a: String = "abc"
a = null// Compile Error

변수가 널을 가질 수 있게 하기 위해서는 변수를 nullable 타입으로 선언해야 합니다.

var b: String? = null // can be null

변수 a의 메소드나 프로퍼티를 호출할 때, NPE가 발생하지 않는 다는것을 보증할 수 있습니다. 따라서 이런 호출은 안전합니다.

println(a.length) // 출력: 3

하지만 b에서 같은 메소드나 프로퍼티를 호출한다면 그 호출은 안전하지 않습니다.

println(b.length) // Compile Error

Checking for null in conditions

변수 b가 널인지 아닌지 명시적으로 확인할 수 있습니다.

val l = if (b != null) b.length else -1

컴파일러는 널체크를 수행한 정보를 추적하고 length 프로퍼티 호출을 허용해줍니다.

val b: String? = "Kotlin"
if (b != null && b.length > 0) {
    print("String of length ${b.length}")
} else {
    print("Empty string")
}

이런 방식은 널 체크 시기와 b 사용되는 시기 사이에 b에 들어있는 값이 변하지 않을 때만 정상적으로 작동합니다. 따라서 b가 변수(var)일 때는 null 체크와 사용 시기 사이에 b가 다시 null이 될 가능성이 있으므로 안전하지 않습니다.

Safe calls

nullable 변수의 프로퍼티나 메소드에 접근하는 또 다른 방법은 안전한 호출 연산자(?.)를 사용하는 것입니다.

val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // Unnecessary safe call

b?.length는 만약 b가 null이 아니라면 b.length값을 반환합니다. 그렇지 않으면 null을 반환합니다.

안전한 호출은 연쇄 호출에서 유용합니다. 예를 들어, Bob은 어떤 부서에 속했을수도 있고 아닐수도 있는 직원입니다. 그 부서에는 Bob의 상사가 있을수도 있고 없을수도 있습니다. Bob이 속한 부서에 있는 상사의 이름을 알고 싶으면 아래와 같이 표현할 수 있습니다.

bob?.department?.head?.name

이런 호출에서 하나의 프로퍼티가 null이라면 반환값은 null이 됩니다.

어떠한 작업에 대해서 null이 아닌 값에 대해서만 수행하고 싶다면 let을 ?.과 함께 사용할 수 있습니다.

val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
    item?.let { println(it) } // prints Kotlin and ignores null
}

안전한 호출(?.)은 대입의 좌항에서도 존재할 수 있습니다. 이런 경우 하나의 호출이라도 null이면 우항은 실행되지 않습니다.

// If either `person` or `person.department` is null, the function is not called:
person?.department?.head = managersPool.getManager()

Elvis operator

엘비스 연산자(?:)를 사용하면 널체크를 보다 간결하게 할 수 있다.

fun main() {
    val b: String? = null

    val l = b?.length ?: -1
    print(l) // -1
}

엘비스 연산자의 좌항이 null이 아니라면 엘비스 연산자는 그 값을 반환합니다. 우항이 null이라면 우항의 값을 반환합니다.
우항의 식은 오직 좌항이 null일 때에만 계산됩니다.

코틀린에서 throw나 return은 식이기 때문에 엘비스 연산자의 우항에서 사용할 수 있습니다.

fun foo(node: Node): String? {
    val parent = node.getParent() ?: return null
    val name = node.getName() ?: throw IllegalArgumentException("name expected")
    // ...
}

The !! operator

널 아님을 단언하는 연산자(!!)는 모든 유형을 non-null 타입으로 변환하고 만약 null인 경우 예외를 던집니다. b!!는 non-null 타입의 b를 반환합니다. 하지만 b가 null이라면 NullPointerException을 throw합니다.

var b: String? = "abc"
val r = b!!.length // 3
b = null
val l = b!!.length // Error

Safe casts

일반적인 캐스팅에서 타겟 타입으로 객체를 변환하지 못하는 경우 ClassCastException이 발생한다. 하지만 안전한 캐스트(as?)를 사용하면 변환하지 못하는 경우 null을 반환한다.

fun main() {
    val a = "14"
    val aInt: Int? = a as? Int

    print(aInt) // null
}

Collections of a nullable type

컬렉션의 요소가 nullable타입이라면, filterNotNull을 사용하여 null이 아닌 인자만 추출할 수 있다.

fun main() {
    val nullableList: List<Int?> = listOf(1, 2, null, 4)
    val intList: List<Int> = nullableList.filterNotNull()

    print(intList) // [1, 2, 4]
}

출처

코틀린 공식문서

profile
수신제가치국평천하

0개의 댓글

관련 채용 정보