널 안정성이란?
널(null)이란 객체가 선언되었지만 초기화되지 않은 상태를 의미합니다. 객체는 데이터가 저장된 주소를 참조하므로 흔히 참조 변수라고 합니다. 데이터가 메모리에 저장되면 어디에 저장됐는지 알아야 이용할 수 있는데 이때 해당 메모리 위치를 식별하는 것이 주소입니다. 객체에는 주소가 저장되며 이 주소로 메모리에 접근해서 데이터를 이용합니다. 그런데 널은 객체가 주소를 가지지 못한 상태를 나타냅니다.
널인 상태의 객체를 이용하면 널 포인트 예외(NullPointException)가 발생합니다. 널인 상태의 객체를 이용할 수 없다는 에러입니다. 이때 널 안정성이란 널 포인트 예외가 발생하지 않도록 코드를 작성하는 것을 말합니다. 당연히 널 포인트 예외가 발생하지 않도록 작성해야 하지만 중요한 것은 이런 노력을 누가 하는가입니다.
// 널 안정성을 개발자가 고려한 코드
fun main() {
var data: String? = null
val length = if (data == null) {
0
} else {
data.length
}
println("data length : $length")
}
// 실행 결과
data length : 0
위 코드에서 String 타입의 data 변수를 null로 선언했습니다. data 변수에 대입한 문자열의 개수를 얻고자 length를 이용했는데 data가 null인 상태에서 data.length 코드를 실행하면 널 포인트 예외가 발생합니다.
따라서 if 문으로 data가 null인지 확인한 뒤 nulll이면 0을 반환하고 null이 아니면 data.length를 반환하게 작성했습니다. 이렇게 하면 data가 null이어도 널 포인트 예외가 발생하지 않습니다. 그런데 이렇게 널 안정성을 개발자의 몫으로 남겨두면 객체가 null인지 일일이 점검하는 코드를 작성해야 합니다. 하지만 위 소스를 다음처럼 작성할 수도 있습니다.
// 코틀린이 제공하는 널 안정성 연산자를 이용한 코드
fun main() {
var data: String? = null
println("data length : ${data?.length ?: 0}")
}
// 실행 결과
data length : 0
data가 null이면 0을 반환하고 null이 아니면 length를 이용해 문자열의 개수를 얻습니다. 앞선 코드와 똑같이 동작하지만 코드에서 직접적으로 null을 점검하지는 않습니다.
널 안정성 연산자
널 허용 연산자 ?
코틀린에서는 변수 타입을 널 허용(nullable)과 널 불허(not null)로 구분합니다. 변수에 null을 대입할 수 있는지를 선언할 때 타입으로 구분합니다. 아래 코드에서 data1 변수는 String 타입으로 선언했습니다. 이렇게 하면 널 불허로 선언하므로 data1 변수에 null을 대입하면 오류가 발생합니다. 그런데 data2 변수는 String 타입으로 선언했지만 타입 뒤에 ? 연산자를 추가했으므로 널 허용 변수가 되어 nulll을 대입할 수 있습니다.
// 널 허용과 널 불허
var data1: String = "ohsopp"
data1 = null // 오류
var data2: String? = "ohsopp"
data2 = null // 성공
널 안전성 호출 연산자 ?.
널 불허로 선언한 변수에는 null을 대입할 수 없으므로 널 포인트 예외를 신경 쓸 필요는 없지만, 널 허용으로 선언한 변수는 null을 대입할 경우 얼마든지 널 포인트 예외가 발생할 수 있습니다. 따라서 변수가 널인 상황을 고려해 프로그램을 작성해야 합니다.
다음 코드는 오류가 발생합니다. data 변수에 null이 아닌 데이터를 저장했더라도 변수를 널 허용으로 선언했으므로 언제든지 null이 대입될 수 있습니다. 이처럼 null이 대입될 수 있는 변수를 널 안정성을 고려하지 않고 작성하면 오류가 발생합니다.
// 널 포인트 예외 오류
var data: String? = "ohsopp"
var length = data.length // 오류
따라서 널 허용으로 선언한 변수의 멤버에 접근할 때는 반드시 ?. 연산자를 이용해야 합니다. ?. 연산자는 변수가 null이 아니면 멤버에 접근하지만 null이면 멤버에 접근하지 않고 null을 반환합니다. ?. 연산자 덕분에 널 허용 변수에 null이 대입되더라도 멤버에 접근하지 않고 null을 반환합니다. 따라서 널 포인트 예외는 발생하지 않습니다.
// 널 안정성 호출 연산자 사용
var data: String? = "ohsopp"
var length = data?.length // 성공
엘비스 연산자 ?:
엘비스 연산자는 ?: 기호를 말합니다. 이 연산자는 변수가 널이면 널을 반환합니다. 그런데 어떤 경우에는 변수가 널일 때 대입해야 하는 값이나 실행해야 하는 구문이 있을 수도 있습니다. 이때 엘비스 연산자를 이용합니다.
// 엘비스 연산자 사용
fun main() {
var data: String? = "ohsopp"
println("data = $data : ${data?.length ?: -1}")
data = null
println("data = $data : ${data?.length ?: -1}")
}
// 실행 결과
data = ohsopp : 6
data = null : -1
예외 발생 연산자 !!
!!는 객체가 널일 때 예외를 일으키는 연산자입니다. 객체가 널일 때 ?. 또는 ?: 연산자를 이용해 널 포인트 예외가 발생하지 않게 작성할 수도 있지만, 또 어떤 경우에는 널 포인트 예외를 발생시켜야 할 때도 있습니다. 이때 !! 연산자를 이용합니다.
// 예외 발생 연산자
fun some(data: String?): Int {
return data!!.length
}
fun main() {
println(some("ohsopp"))
println(some(null))
}
// 실행 결과
6
Exception in thread "main" java.lang.NullPointerException
위 소스는 some() 함수에 문자열을 전달하면 오류 없이 정상적으로 실행되고, null을 전달하면 data!!.length 코드로 예외 메시지를 출력하는 것을 보여줍니다.
'📌 Kotlin' 카테고리의 다른 글
[Kotlin] List / MutableList 추가, 삭제 (0) | 2022.02.19 |
---|