Skip to content

널 안전성

코틀린에서는 null 값을 가질 수 있습니다. 코틀린은 어떤 값이 없거나 아직 설정되지 않았을 때 null 값을 사용합니다. 컬렉션 챕터에서 맵에 존재하지 않는 키로 키-값 쌍에 접근하려고 했을 때, 코틀린이 null 값을 반환하는 예시를 이미 보셨을 겁니다. 이러한 방식으로 null 값을 사용하는 것은 유용하지만, 코드가 null 값을 처리할 준비가 되어 있지 않다면 문제가 발생할 수 있습니다.

프로그램에서 null 값으로 인한 문제를 방지하기 위해 코틀린에는 널 안전성(Null safety)이 갖추어져 있습니다. 널 안전성은 런타임이 아닌 컴파일 시점에 null 값으로 인한 잠재적인 문제를 감지합니다.

널 안전성은 다음과 같은 기능을 제공하는 여러 기능의 조합입니다.

  • 프로그램에서 null 값이 허용되는 시점을 명시적으로 선언합니다.
  • null 값을 확인합니다.
  • null 값을 포함할 수 있는 프로퍼티나 함수에 안전 호출을 사용합니다.
  • null 값이 감지되었을 때 취할 동작을 선언합니다.

널 허용 타입

코틀린은 선언된 타입이 null 값을 가질 가능성을 허용하는 널 허용 타입(nullable types)을 지원합니다. 기본적으로 타입은 null 값을 허용하지 않습니다. 널 허용 타입은 타입 선언 뒤에 ?를 명시적으로 추가하여 선언합니다.

예시:

kotlin
fun main() {
    // neverNull은 String 타입입니다
    var neverNull: String = "This can't be null"

    // 컴파일러 오류 발생
    neverNull = null

    // nullable은 널 허용 String 타입입니다
    var nullable: String? = "You can keep a null here"

    // 이것은 괜찮습니다
    nullable = null

    // 기본적으로 null 값은 허용되지 않습니다
    var inferredNonNull = "The compiler assumes non-nullable"

    // 컴파일러 오류 발생
    inferredNonNull = null

    // notNull은 null 값을 허용하지 않습니다
    fun strLength(notNull: String): Int {                 
        return notNull.length
    }

    println(strLength(neverNull)) // 18
    println(strLength(nullable))  // 컴파일러 오류 발생
}

length는 문자열 내의 문자 수를 포함하는 String 클래스의 프로퍼티입니다.

널 값 확인

조건식 내에서 null 값의 존재 여부를 확인할 수 있습니다. 다음 예시에서 describeString() 함수는 maybeStringnull아니고 길이가 0보다 큰지 확인하는 if 문을 포함합니다.

kotlin
fun describeString(maybeString: String?): String {
    if (maybeString != null && maybeString.length > 0) {
        return "String of length ${maybeString.length}"
    } else {
        return "Empty or null string"
    }
}

fun main() {
    val nullString: String? = null
    println(describeString(nullString))
    // 비어 있거나 null인 문자열
}

안전 호출 사용

null 값을 포함할 수 있는 객체의 프로퍼티에 안전하게 접근하려면 안전 호출 연산자 ?.를 사용합니다. 안전 호출 연산자는 객체 또는 접근된 프로퍼티 중 하나라도 null이면 null을 반환합니다. 이는 null 값으로 인해 코드에서 오류가 발생하는 것을 방지하려는 경우 유용합니다.

다음 예시에서 lengthString() 함수는 안전 호출을 사용하여 문자열의 길이 또는 null을 반환합니다.

kotlin
fun lengthString(maybeString: String?): Int? = maybeString?.length

fun main() { 
    val nullString: String? = null
    println(lengthString(nullString))
    // null
}

안전 호출은 체인으로 연결할 수 있으므로, 객체의 어떤 프로퍼티라도 null 값을 포함하면 오류 발생 없이 null이 반환됩니다. 예시:

kotlin
  person.company?.address?.country

안전 호출 연산자는 확장 함수 또는 멤버 함수를 안전하게 호출하는 데에도 사용될 수 있습니다. 이 경우, 함수가 호출되기 전에 널 검사가 수행됩니다. 검사에서 null 값이 감지되면 호출은 건너뛰어지고 null이 반환됩니다.

다음 예시에서 nullStringnull이므로 .uppercase() 호출이 건너뛰어지고 null이 반환됩니다.

kotlin
fun main() {
    val nullString: String? = null
    println(nullString?.uppercase())
    // null
}

엘비스 연산자 사용

null 값이 감지되었을 때 반환할 기본값을 엘비스 연산자 ?:를 사용하여 제공할 수 있습니다.

엘비스 연산자의 왼쪽에 null 값을 검사할 대상을 작성합니다. 엘비스 연산자의 오른쪽에 null 값이 감지되었을 때 반환할 값을 작성합니다.

다음 예시에서 nullStringnull이므로 length 프로퍼티에 접근하는 안전 호출은 null 값을 반환합니다. 결과적으로 엘비스 연산자는 0을 반환합니다.

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

코틀린의 널 안전성에 대한 더 자세한 정보는 널 안전성을 참조하세요.

연습

연습 문제

회사 직원 데이터베이스에 접근할 수 있는 employeeById 함수가 있습니다. 아쉽게도 이 함수는 Employee? 타입의 값을 반환하므로 결과가 null일 수 있습니다. 당신의 목표는 직원의 id가 주어졌을 때 해당 직원의 급여를 반환하거나, 데이터베이스에 직원이 없는 경우 0을 반환하는 함수를 작성하는 것입니다.

kotlin
data class Employee (val name: String, var salary: Int)

fun employeeById(id: Int) = when(id) {
    1 -> Employee("Mary", 20)
    2 -> null
    3 -> Employee("John", 21)
    4 -> Employee("Ann", 23)
    else -> null
}

fun salaryById(id: Int) = // Write your code here

fun main() {
    println((1..5).sumOf { id -> salaryById(id) })
}
예시 솔루션
kotlin
data class Employee (val name: String, var salary: Int)

fun employeeById(id: Int) = when(id) {
    1 -> Employee("Mary", 20)
    2 -> null
    3 -> Employee("John", 21)
    4 -> Employee("Ann", 23)
    else -> null
}

fun salaryById(id: Int) = employeeById(id)?.salary ?: 0

fun main() {
    println((1..5).sumOf { id -> salaryById(id) })
}