자바의 경우 int, boolean과 같은 primitive type을 제외한 객체들은 항상 null이 될 수 있습니다. 코틀린은 자바와 다르게 Nullable과 Non-nullable 타입으로 프로퍼티를 선언할 수 있습니다. Non-nullable 타입으로 선언하면 객체가 null이 아닌 것을 보장하기 때문에 null check 등의 코드를 작성할 필요가 없습니다.
이 글에서는 코틀린에서 Null 객체를 다루는 방법들을 소개합니다. 먼저 Nullable과 Non-nullable 타입의 차이점에 대해서 알아보고, 안전하게 null을 처리하는 방법들에 대해서 알아볼 것입니다.
Nullable과 Non-Nullable 프로퍼티
코틀린에 익숙하시다면 차이점에 대해서 아실 것입니다. 타입을 선언할 때 ?
를 붙이면 null을 할당할 수 있는 프로퍼티이고, ?
가 붙지 않으면 null이 허용되지 않는 프로퍼티를 의미합니다.
var nullable: String? = "nullable"
var nonNullable: String = "non-Nullable"
nullable 프로퍼티는 null을 할당할 수 있지만, nonNullable에 null을 할당하려고 하면 컴파일 에러가 발생합니다.
nullable = null // 컴파일 성공
nonNullable = null // 컴파일 에러
코틀린은 nullable과 non-nullable 개념을 만들어, null에 안전한 프로그램을 만들 수 있게 도와줍니다. 그래서 코틀린만 사용한다면 Null Pointer Exception 같은 예외가 발생하지 않을 수 있습니다. 또한, 자바처럼 try-catch 구문을 많이 쓰지 않아도 됩니다.
코틀린에서 Null Pointer Exception(NPE)이 발생하는 경우
코틀린에서는 NPE가 발생하지 않을 것 같지만 자바의 라이브러리를 쓰는 경우 NPE가 발생할 수 있습니다. 자바에서는 non-nullable 타입이 없기 때문에 자바 라이브러리를 사용할 때 nullable 타입으로 리턴됩니다.
nullable 타입을 non-nullable 타입으로 변경하기
코틀린에서 아래와 같은 자바 라이브러리를 사용한다고 가정하겠습니다.
이 함수는 String을 리턴하며, 코틀린에서는 이 타입을 nullable인 String?
으로 인식합니다.
String getString() {
String str = "";
....
return str;
}
코틀린에서 이 함수의 리턴 값을 non-nullable인 String
으로 변환하고 싶습니다.
그럼 이 프로퍼티가 항상 null이 아닌 것을 보장할 수 있고, try-catch 로 NPE를 처리하지 않아도 됩니다.
하지만 아래 코드처럼 대입하면 컴파일 에러가 발생합니다. String?
타입을 String
타입에 할당하려고 했기 때문입니다.
var nonNullString1: String = getString() // 컴파일 에러
반면에 아래 코드는 컴파일됩니다. 그 이유는 !!
연산자를 사용했기 때문입니다.
!!
연산자는 객체가 null이 아닌 것을 보장합니다. 만약 null이라면 NPE를 발생시킵니다.
var nonNullString2: String = getString()!! // 컴파일 성공
이런 이유로 !!
연산자는 null이 아닌 것을 보장할 수 있는 객체에만 사용해야 합니다.
안전하게 nullable 프로퍼티 접근하기
코틀린에서 nullable 프로퍼티를 안전하게 접근하는데 사용하는 다양한 방법들에 대해서 알아보겠습니다.
조건문으로 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 루프가 반복되는 경우 가독성을 해칠 수 있습니다.(if-else 루프 지옥)
Safe call 연산자로 nullable 접근
Safe 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-nullable이기 때문입니다.
아래 여러 객체로 둘러 쌓인 String에 접근하는 코드입니다.
a?.b?.c?.d?
를 수행할 때, 이 객체들 중에 null이 있으면 null을 리턴합니다.
println(a?.b?.c?.d?.length)
안전하게 nullable 프로퍼티 할당
어떤 프로퍼티를 다른 프로퍼티에 할당할 때, 객체가 null인 경우 default 값을 할당하고 싶을 수 있습니다. 자바에서는 삼항연산자를 사용하여 아래 코드처럼 객체가 null인 경우 default값을 설정해줄 수 있습니다.
String b = null;
int l = b != null ? b.length() : -1;
하지만 코틀린은 삼항연산자를 지원하지 않습니다. 삼항연산자를 대체할 수 있는 것들에 대해서 알아보겠습니다.
if-else
if-else로 삼항연산자를 대체할 수 있습니다. 자바와는 다르게 코틀린은 한줄로 if-else를 쓸 수 있습니다. 다음은 if-else를 사용하여 삼항연산자와 동일한 내용을 구현한 코드입니다.
val l = if (b != null) b.length else -1
엘비스 연산자(Elvis Operation)
엘비스 연산자는 ?:
를 말합니다. 삼항연산자와 비슷한데 ?:
왼쪽의 객체가 null이 아니면 이 객체를 리턴하고
null이라면 ?:
의 오른쪽 객체를 리턴합니다.
다음은 위의 if-else 예제를 엘비스 연산자를 사용하여 구현한 코드입니다.
val l = b?.length ?: -1
Safe Cast
코틀린에서 형변환할 때 Safe Cast를 이용하면 안전합니다.
아래 코드에서 string
은 문자열이지만 Any
타입입니다.
as?
를 이용하여 String과 Int로 형변환을 시도하고 있습니다. String은 가능하기 때문에 성공하였고, Int는 타입이 맞지 않기 때문에 null을 리턴하였습니다.
val string: Any = "AnyString"
val safeString: String? = string as? String
val safeInt: Int? = string as? Int
println(safeString)
println(safeInt)
실행 결과를 보면 safeInt
는 캐스팅이 실패하여 null이 할당되었습니다.
AnyString
null
Collection의 Null 객체를 모두 제거
Collection에 있는 Null 객체를 미리 제거할 수 있는 함수도 제공합니다.
다음은 List에 있는 null 객체를 filterNotNull
메소드를 이용하여 삭제하는 코드입니다.
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
println(intList)
실행 결과를 보면 null이 제거된 나머지 아이템들만 출력이 됩니다.
[1, 2, 4]
정리
코틀린은 Null에 안전하도록 설계되었습니다. 그래서 프로그램이 null에 안전하게 구현하는 기능들에 대해서 알아보았습니다.
참고
Related Posts
- Kotlin - 배열에서 최소 값, 최대 값 찾기
- Kotlin - 2차원 배열 선언, 초기화 방법
- Kotlin - 배열 선언, 초기화 방법
- Kotlin - 리스트, 배열 길이 가져오기
- Kotlin - 리스트에서 최대, 최소 값 찾기
- Kotlin - for 반복문, 배열/리스트 순회
- Kotlin - Timer, 주기적으로 함수 실행
- Kotlin - sleep, 쓰레드 몇 초 지연
- Kotlin - Thread 생성 및 실행
- Kotlin에서 정규표현식 사용하기
- Kotlin - 문자열 길이 계산
- Kotlin - 문자열 비교 방법(equals, ==, compareTo)
- Kotlin - 2개의 배열 하나로 합치기
- Kotlin - 2개의 List 하나로 합치기
- Kotlin - 디렉토리의 모든 파일 리스트 출력
- Kotlin - 리스트 정렬 방법 (sort, sortBy, sortWith)
- Kotlin - 문자열 뒤집기 (Reverse String)
- Kotlin - 랜덤 숫자 생성 (Random, SecureRandom)
- Kotlin - Range, 숫자 범위 표현
- Kotlin - 음수를 양수로 변환, math.abs()
- Kotlin - List를 Set로 변환
- Kotlin - Set를 List로 변환
- Kotlin - 문자열에서 숫자(int)만 추출하는 방법
- Kotlin - Map을 List로 변환하는 방법
- Kotlin - File, Directory가 존재하는지 확인
- Kotlin - List를 Map으로 변환
- Kotlin - List의 중복 요소 제거
- Kotlin - List를 Array로 변환
- Kotlin - 엘비스 연산자 (Elvis Operation)
- Kotlin - Array를 List로 변환
- Kotlin - String을 Float으로 변환
- Kotlin - String을 Double으로 변환
- Kotlin - String을 Int로 변환
- Kotlin - String을 Long으로 변환
- Kotlin - String Null 또는 Empty 체크