본문 바로가기

KOTLIN

[Kotlin] 코틀린의 Null 안정성

 자바와 마찬기로 코틀린의 참조 값에는 아무 값도 참조하지 않는 null이라는 값이 있습니다. 이 참조 객체는 어떠한 할당도 받지 않았다는 것을 뜻하죠. 자바에서는 모든 참조 타입의 변수에 null을 대입할 수 있지만 이 참조 타입에 정의된 메서드나 프로퍼티를 사용하려 하면 NullPointerException을 발생시킵니다. Java나 JSP 하면서 질리도록 많이 본 기억이 나네요

코틀린 Null 안정성

코틀린은 이런 문제를 해결하기 위해 Nullable Non-Null타입으로 프로퍼티를 선언할 수 있습니다.

 

Nullable

코틀린에서 null이 될 수도 있는 값을 받는 함수를 작성하려면 파라미터 타입 뒤에 ? 를 붙여 null이 될 수 있는 타입으로 지정해야 합니다. 

var nullable: String? = null // 컴파일 성공
var nonNull: String = null // 컴파일 에러

Non-Null

코틀린의 모든 기본 참조 타입은 Non-Null 타입입니다. 따라서 String 같은 타입에 null을 대입할 수 없습니다.

다음 코드는 주어진 문자열이 문자만 들어있는지 검사하는 함수입니다. null을 s 파라미터에 넘기면 컴파일 오류가 발생합니다.

 

String은 null을 받을 수 있는 타입이 아니기 때문입니다. 따라서 isLetterString 함수에 null이 전달되지 않는다는 사실을 보장하므로 함수 자체에서는 추가로 검사할 필요가 없습니다.

fun isLetterString(s: String): Boolean{
	if(s.isEmpty()) return false
	for(ch in s) if (!ch.isLetter()) return false;
    return true;
}

fun main(){
	println(isLetterString("abc") // Compile O
    // error : null can not be a value of a non-null type String
    println(isLetterString(null))
}

 

그렇다면 isLetterString() 같은 함수가 널이 될 수 있는 값으로 처리하게 만드려면 어떻게 해야 할까요?

1. SmatCast

파라미터의 타입을 바꾸지 않아도 null에 대한 검사를 추가하면 컴파일러의 스마트 캐스트라는 기능이 컴파일이 가능하게 해줍니다. null에 대한 동등성 검사를 수행하면 컴파일러의 흐름이 한쪽에는 타겟이 널이고 한쪽에서는 아니라는 사실을 알 수 있습니다. 그 후 컴파일러는 이 정보를 사용해 값 타입을 세분화 함으로써 Nullable값을 Non-NullCast 합니다.

fun isLetterString(s: String?): Boolean{
    if(s == null) return false
    
    if(s.isEmpty()) return false
    
    for(ch in s) if(!ch.isLetter()) return false
    
    return true
}

인텔리 J 플러그인은 툴팁으로 세분화된 타입을 표시해줍니다.

2. Not-Null assertion( ! ! )

!! 연산자는 KotlinNullPointerException을 일부러 발생시킬 수 있는 연산자 입니다. 즉, 어떤 값이던 non-null 타입으로 바꿀 수 있습니다. 이 연산자가 붙은 타입은 본래 타입이 Null이 될 수 없는 버전입니다. 

fun main(){
    var name: String? = null
    fun init(){
        name = "jhon"
    }
    
    fun sayHello(){
        print(name!!.uppercase())
    }
    
    init()
    sayHello()
}

2. 엘비스 연산자 ( ?: )

이항 연산자로 좌항을 계산한 값이 널인지 검사한다. 좌항 값이 null이 아니면 좌항 값을 결과로 하고, 좌항 값이 null이면 우항 값을 결과로 사용 합니다.

val n = readLine()?.toInt() ?: 0