널 안전성

널 안전성 (Null safety)

Kotlin에서는 null 값을 가질 수 있어요. 무언가가 없거나 아직 설정되지 않았을 때 Kotlin은 null 값을 사용해요. 앞선 Collections 챕터에서 맵에 없는 키로 키-값 쌍에 접근하려 할 때 Kotlin이 null 값을 돌려주는 예를 이미 봤죠. 이렇게 null 값을 쓰는 게 유용하긴 하지만, 코드가 그런 경우를 처리할 준비가 안 되어 있으면 문제가 생길 수 있어요.

프로그램에서 null 값 때문에 생기는 문제를 막기 위해 Kotlin은 널 안전성(null safety)을 갖추고 있어요. 널 안전성은 null 값의 잠재적 문제를 실행 시점이 아니라 컴파일 시점에 잡아내요.

널 안전성은 다음을 할 수 있게 해 주는 기능들의 조합이에요.

  • 프로그램에서 null 값이 허용되는 시점을 명시적으로 선언해요.
  • null 값을 확인해요.
  • null 값일 수 있는 속성이나 함수에 안전 호출(safe call)을 사용해요.
  • null 값이 감지되면 수행할 동작을 선언해요.

출처: Kotlin 공식 문서

본문

널 가능 타입 (Nullable types)

Kotlin은 널 가능 타입(nullable types)을 지원해서, 선언된 타입이 null 값을 가질 수 있게 해 줘요. 기본적으로 타입은 null 값을 받아들이지 않아요. 널 가능 타입은 타입 선언 뒤에 ?를 명시적으로 붙여서 선언해요.

예를 들어 이렇게요.

fun main() {
    // neverNull has String type
    var neverNull: String = "This can't be null"

    // Throws a compiler error
    neverNull = null

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

    // This is OK
    nullable = null

    // By default, null values aren't accepted
    var inferredNonNull = "The compiler assumes non-nullable"

    // Throws a compiler error
    inferredNonNull = null

    // notNull doesn't accept null values
    fun strLength(notNull: String): Int {                 
        return notNull.length
    }

    println(strLength(neverNull)) // 18
    println(strLength(nullable))  // Throws a compiler error
}

length는 문자열 안에 있는 문자의 개수를 담고 있는 String 클래스의 속성이에요.

널 값 확인하기 (Check for null values)

조건식 안에서 null 값의 존재를 확인할 수 있어요. 다음 예제에서 describeString() 함수에는 maybeStringnull이 아닌지, 그리고 그것의 length가 0보다 큰지 확인하는 if 문이 있어요.

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))
    // Empty or null string
}

안전 호출 사용하기 (Use safe calls)

null 값을 가질 수 있는 객체의 속성에 안전하게 접근하려면 안전 호출 연산자 ?.를 사용해요. 안전 호출 연산자는 객체나 객체에서 접근한 속성 중 하나가 null이면 null을 돌려줘요. 코드에서 null 값 때문에 오류가 발생하는 걸 피하고 싶을 때 유용해요.

다음 예제에서 lengthString() 함수는 안전 호출을 사용해서 문자열의 길이 또는 null을 돌려줘요.

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

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

안전 호출은 연결(chaining)해서 쓸 수 있어요. 객체의 어떤 속성이 null을 포함하면 오류가 발생하지 않고 null이 돌아오죠. 예를 들어 이렇게요.

  person.company?.address?.country

안전 호출 연산자는 확장 함수나 멤버 함수를 안전하게 호출하는 데도 쓸 수 있어요. 이 경우 함수를 호출하기 전에 널 검사를 수행해요. 검사에서 null 값이 감지되면 호출을 건너뛰고 null을 돌려줘요.

다음 예제에서 nullStringnull이라 .uppercase() 호출을 건너뛰고 null을 돌려줘요.

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

엘비스 연산자 사용하기 (Use Elvis operator)

null 값이 감지되면 돌려줄 기본값을 엘비스 연산자(Elvis operator) ?:로 제공할 수 있어요.

엘비스 연산자 왼쪽에는 null 값을 확인할 대상을 쓰고, 오른쪽에는 null 값이 감지되면 돌려줄 값을 써요.

다음 예제에서 nullStringnull이라서 length 속성에 접근하는 안전 호출이 null 값을 돌려줘요. 그 결과 엘비스 연산자가 0을 돌려주죠.

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

Kotlin에서 널 안전성에 대한 더 자세한 내용은 Null safety 문서를 참고해요.

연습 (Practice)

연습문제 (Exercise)

회사의 직원 데이터베이스에 접근할 수 있게 해 주는 employeeById 함수가 있어요. 그런데 이 함수는 Employee? 타입의 값을 돌려주기 때문에, 결과가 null일 수 있어요. 목표는 id가 주어졌을 때 직원의 급여를 돌려주거나, 직원이 데이터베이스에 없으면 0을 돌려주는 함수를 작성하는 거예요.

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)
    else -> null
}

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

fun main() {
    println((1..5).sumOf { id -> salaryById(id) })
}
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)
    else -> null
}

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

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

다음 단계 (What's next?)

축하해요! 이제 입문 투어를 모두 마쳤어요. 중급 투어를 통해 Kotlin에 대한 이해를 한 단계 더 끌어올려 볼까요.