코틀린에서는 자바보다 null 처리를 좀 더 명확하게 한다.
따라서 NPE(Null Pointer Exception)가 발생하는 빈도를 현저히 낮출 수 있다.
이 글에서는 자바와 비교하여 코틀린에서 null을 처리하는 다양한 방법에
대해서 살펴볼 예정이다.
자바의 경우 int, boolean과 같은 primitive type을 제외한 객체들은 항상 null이 될 수 있다.
public int getLen(String str) {
return str.lengh();
}
자바에서 위 함수는 컴파일시 문제없이 빌드 되지만, runtime에 인자로 null이 들어오게 되면 NPE가 발생한다.
코틀린은 자바와 다르게 Nullable과 Non-nullable 타입으로 프로퍼티를
선언할 수 있다.
Non-nullable 타입으로 선언하면 객체가 null이 아닌 것을 보장하기 때문에
null check등의 코드를 작성할 필요가 없다.
타입을 선언할 때 ?를 붙이면 null을 할당할 수 있는 프로퍼티이고, ?가 붙지 않으면 null이 허용되지 않는 프로퍼티를 의미한다.
nullable 프로퍼티는 null을 할당할 수 있지만, nonNullable에 null을 할당하려고 하면 컴파일 에러가 발생한다.
val nullable: String? = null // 컴파일 성공
val nonNullable: String = null // 컴파일 에러
아래와 같이 Int? 를 통해 Null 혹은 Int 값이 반환될 수 있음을 명시한다.
fun parseInt2(str: String): Int? {
val numberList = listOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
if(numberList.contains(str)){
return parseInt(str)
} else{
return null
}
}
위의 방법 외에도 null을 처리하는 다양한 방법에 대해서 더 자세히 알아보자.
코틀린은 nullable과 non-nullable 개념을 만들어, null에 안전한 프로그램을
만들 수 있게 도와준다. 그래서 코틀린만
사용한다면 Null Pointer Exception 같은 예외가 발생하지 않을 수도 있다.
하지만, 코틀린에서는 NPE가 발생하지 않을 것 같지만 자바의 라이브러리를 쓰는
경우 NPE가 발생할 수 있다.
자바에서는 non-nullable 타입이 없기 때문에
자바 라이브러리를 사용할 때 nullable 타입으로 리턴 된다.
코틀린에서 아래와 같은 자바 라이브러리를 사용한다고 가정해보자.
이 함수는 String을 리턴하며, 코틀린에서는 이 타입을 nullable인
String? 으로 인식한다.
String getString() {
String str = "";
....
return str;
}
코틀린에서 이 함수의 리턴 값을 non-nullable인 String으로 변환하고 싶다면 어떻게 할까?
아래와 같이 String? 타입을 String 타입에 할당하려고 하면
컴파일 에러를 발생 시킨다.
var nonNullString1: String = getString() // 컴파일 에러
반면에 아래 코드는 컴파일이 된다. 그 이유는 !! 연산자를 사용했기 때문이다.
!! 연산자는 객체가 null이 아닌 것을 보장한다. 만약 null이라면 NPE를
발생시킨다.
var nonNullString2: String = getString()!! // 컴파일 성공
이런 이유로 !! 연산자는 null이 아닌 것을 보장할 수 있는
객체에만 사용해야 한다.
코틀린에서 nullable 프로퍼티를 사용할 때 안전하게 사용하는 다양한 방법들에 대해서 알아보자.
가장 쉬운 방법은 if-else를 이용하는 것이다. 자바에서는 흔히
사용하는 방식이다.
아래 코드는 String?을 접근하기 전에 if로 null을 체크하는 코드이다.
val b: String? = "Kotlin"
if (b != null && b.length > 0) {
print("String of length ${b.length}")
} else {
print("Empty string")
}
단점은 if-else 루프가 반복되는 경우 가독성을 해칠 수 있다.
Safa call은 객체를 접근 할 때 ?. 로 접근하는 방법을 말한다.
예를 들어 아래 코드에서 b?.length를 수행 할 때 b가 null이라면 length를
호출하지 않고 null을 리턴한다.
그렇기 때문에 NPE가 발생하지 않는다.
val a: String = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // Unnecessary safe call
위의 코드에서 a?.length는 불필요하게 Safe call을 사용하고 있다.
a는 non-nullalble이기 때문이다.
아래와 같이 또 다른 예시를 보면, 여러 객체로 둘러 쌓인 String에
접근하는 코드이다. a?.b?.c?.d?. 를 수행할 때, 이 객체들 중에 null이
있으면 null을 리턴하게 된다.
println(a?.b?.c?.d?.length)
어떤 프로퍼티를 다른 프로퍼티에 할당할 때, 객체가 null인 경우 default 값을 할당하고 싶을 수 있다. 자바에서는 삼항연산자를 사용하여 아래 코드처럼 객체가 null인 경우 default 값을 설정해줄 수 있다.
String b = null;
int l = (b != null) ? b.length() : -1;
하지만 코틀린은 삼항연산자를 지원하지 않는다. 삼항 연산자를 대체할 수 있는
것들에 대해서 살펴보자.
val l = if (b != null) b.length else -1
엘비스 연산자(Elvis Operation)
val l = b?.length ?: -1
Safe Cast
val string: Any = "AnyString"
val safeString: String? = string as? String
val safeInt: Int? = string as? Int
println(safeString) // AnyString
println(safeInt) // null
Collection에 있는 null 객체를 미리 제거할 수 있는 함수도 제공한다.
다음은 List에 있는 null 객체를 filterNotNull 메서드를 이용하여 삭제하는
코드이다.
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
println(intList) // [1, 2, 4]
실행 결과를 보면 null이 제거된 나머지 아이템들만 출력된다.
Reference
https://codechacha.com/ko/kotlin-null-safety/
https://tourspace.tistory.com/114