Kotlin - 코틀린에서의 Null
들어가며
자바를 포함한 많은 프로그래밍에서의 가장 일반적인 오루 중 하나는 Null 참조의 멤버에 엑세스하여 생기는 Null reference exception의 발생이다. 코틀린에서의 타입 시스템은 코드에서 Null references 위험을 제거하는 것을 목표로 하며 런타임 단계가 아닌 컴파일 단계에서 오류를 미리 감지해서 예외를 줄일 수 있도록 하였다.
코틀린에서 NullPointerException이 발생할 수 있는 경우
- throw NullPointterException() : 명시적으로 호출하는 경우
- !! 연산자 사용
- 초기화에 관한 데이터 불일치
ex1) : 생성자에서 사용할 수 있는 초기화되지 않은 this가 전달되어 어딘가에 사용되는 경우
ex2) : superclass 생성자는 파생 클래스에서 구현이 초기화되지 않은 상태를 사용하는 open member를 호출
- 코틀린과 자바를 함께 사용하는 경우
ex1) : 플랫폼 타입의 null 참조로 멤버에 대한 엑세스 시도
ex2) : 자바에서 MutableList와 같은 non-Null type에 null을 추가하려는 경우
ex3) : 외부 java 코드로 인한 기타 문제
코틀린에서의 null 타입
코틀린에서는 null 이슈를 제거하기 위해 코틀린 타입 시스템이 null이 될 수 있는 타입을 명시적으로 지원한다는 점이다. 이 말은 즉, null이 될 수 있는 타입은 프로퍼티나 변수가 null을 허용하게 만드는 방법이다. 기본적으로 자바와 같이 타입만 적을 시에는 코틀린에서는 null을 허용하지 않는다. 그래서 명시적으로 적어야 하는 것이다.
코틀린에서의 Null 참조 구분
코틀린 타입 시스템은 null을 저장할 수 있는 참조와 null이 아닌 참조를 포함할 수 없는 참조를 구별한다.
ex) 문자열 유형의 일반 변수는 null을 포함할 수 없는 경우
var a: String = "abc" // Regular initialization means non-null by default
a = null // compilation error
ex) null을 허용하기 위해 null문자열로 String? 와 같이 선언과 동시에 abc 로 초기화
var b: String? = "abc" // can be set null
b = null // ok
ex) null 이 아니면 length 반환, 그렇지 않으면 null을 반환하는 경우
val b: String? = null
println(b?.length)
코틀린에서의 null check 방법
1. 조건문으로 Null check - 자바와 같이 명시적으로 null 체크를 수행
val b: String? = "Kotlin"
if (b != null && b.length > 0) {
print("${b.length}")
}
2. safe call 연산자 - ?.
- b가 null이 아니면 b.length를 반환하고, 그렇지 않으면 null을 반환
val a = "Kotlin"
val b: String? = null
print(b?.length)
print(a?.length) // Unnecessary safe call
- Null이 아닌 값에 대해서만 특정 작업을 수행하려면 다음과 같이 safe call 연산자를 사용할 수 있다.
val listWithNulls : List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { print(it)}
}
3. 엘비스 ?: 연산자
- 엘비스 연산자는 널값을 허용하지 않는 변수에 널 값이 들어 갔을때 널 값을 변환할 수 있는 함수의 결과를 만들어 준다.
class Elvis {
var yts: String? = null
fun elvis() {
val name: String = yts ?: "YTS" //yts라는 변수는 현재 null 이기때문에 결과적으로 name에 'YTS'라는 값이 들어감.
val nameTwo: String = yts ?: return //함수자체를 return 시키도록 만들 수도 있음.
val nameThree: String =
yts ?: throw NullPointerException() //yts라는 변수는 현재 null 이기때문에 결과적으로 NullPointerException 예외가 발생.
}
}
4. 널 값 보증 !! 연산자
- 상황에 따라 Null이 아닌 값만 포함되는 경우가 생겼을 때 사용한다. 이때 !! 연산자를 이용하여 임의로 널값이 안들어온다는 보증을 해줄 수 있다. 하지만 널값이 들어오게 되면 오류가 발생하므로 사용시 유의
fun nonNull() {
val animal: String = "호랑이"
val animalName: String = animal!! // animal은 절대 null값이 아니기때문에 !!연산자로 보증.
}