Kotlin은 Java와 달리 널 참조(Null Reference)로 인한 문제를 방지하기 위해 강력한 널 안전성 기능을 제공합니다. 오늘은 널 안전성의 기본 개념부터 Nullable과 Non-nullable 타입, Elvis 연산자, 그리고 널 처리 방법에 대해 알아보겠습니다.
널 참조는 프로그래밍에서 오류의 중요한 원인 중 하나입니다. "널 포인터 예외"는 개발자가 코드를 작성하면서 가장 많이 만나는 런타임 오류 중 하나이며, 이를 방지하기 위해 Kotlin은 명시적인 널 처리 메커니즘을 제공합니다.
Nullable과 Non-nullable 타입
Kotlin에서는 기본적으로 모든 변수는 Non-nullable입니다. 즉, 변수를 초기화할 때 널 값을 할당할 수 없고, 해당 변수는 항상 실제 값을 가지도록 강제됩니다. 이는 프로그램 실행 중 널 참조로 인해 발생하는 오류를 미리 예방할 수 있는 강력한 장치입니다.
예를 들어, 다음과 같은 코드를 보겠습니다.
var name: String = "Kotlin"
name = null // 컴파일 오류 발생
위 코드는 name
변수에 널 값을 할당하려고 시도하면서 컴파일 오류가 발생합니다. Kotlin은 변수에 널 할당을 방지하기 위해 기본적으로 모든 변수를 Non-nullable로 처리합니다. 만약 변수가 널을 가질 수 있도록 하려면 타입 뒤에 ?
를 붙여야 합니다.
var name: String? = "Kotlin"
name = null // 정상 작동
String?
타입은 String
값이나 null
을 가질 수 있는 Nullable 타입입니다. 이를 통해 명시적으로 널 가능성을 표현할 수 있습니다.
Null 처리 방법
Nullable 타입의 변수를 사용할 때는 반드시 널 처리를 해줘야 합니다. Kotlin은 안전한 널 처리를 위해 다양한 기능을 제공합니다.
1. 안전한 호출 연산자 (?.
)
안전한 호출 연산자는 Nullable 타입의 변수를 사용할 때 널 체크를 자동으로 해줍니다. 변수 뒤에 ?.
를 붙이면 해당 변수의 값이 널이 아닐 경우에만 그 뒤의 메서드나 프로퍼티에 접근하게 됩니다.
val length: Int? = name?.length
위 코드에서 name
이 널인 경우, name?.length
는 널을 반환하고, 널이 아닌 경우에만 length
를 반환합니다. 이를 통해 널 체크를 간편하게 할 수 있습니다.
2. 엘비스 연산자 (?:
)
엘비스 연산자는 Nullable 타입의 값이 널일 경우에 대체 값을 지정하는 데 사용됩니다. 널을 허용하는 변수를 사용할 때, 해당 값이 널인 경우에 대비해 기본값을 설정할 수 있습니다.
val length: Int = name?.length ?: 0
위 코드에서 name?.length
가 널인 경우, length
변수에는 0
이 할당됩니다. 엘비스 연산자는 널 값을 처리하는 데 매우 유용하게 사용됩니다.
3. 널 아님 단언 (!!
)
널 아님 단언 연산자인 !!
를 사용하면 해당 값이 절대 널이 아니라고 Kotlin에게 단언할 수 있습니다. 만약 단언한 값이 널일 경우, 런타임에서 NullPointerException
이 발생하게 됩니다.
val length: Int = name!!.length
이 방식은 값이 절대 널이 아님을 확신할 때 사용할 수 있지만, 잘못 사용하면 런타임 오류를 발생시킬 수 있으므로 주의가 필요합니다.
Nullable과 Non-nullable 타입의 사용 예제
아래는 Nullable과 Non-nullable 타입을 활용한 예제입니다.
fun main() {
var nullableName: String? = "Kotlin"
var nonNullableName: String = "Hello"
// 안전한 호출 연산자 사용
println(nullableName?.length) // 출력: 6
// 엘비스 연산자 사용
val length = nullableName?.length ?: -1
println("Length: $length") // 출력: Length: 6
// 널 아님 단언 사용
nullableName = null
try {
println(nullableName!!.length) // NullPointerException 발생
} catch (e: NullPointerException) {
println("널 포인터 예외 발생: ${e.message}")
}
}
위 예제에서는 nullableName
이라는 Nullable 타입의 변수를 사용하여 널 처리 방법을 다양하게 보여주고 있습니다. 안전한 호출 연산자와 엘비스 연산자를 사용하여 널을 안전하게 처리하고, 널 아님 단언을 사용했을 때 NullPointerException
이 발생하는 것을 확인할 수 있습니다.
요약
Kotlin의 널 안전성은 개발자가 널 포인터 예외를 사전에 방지하고 안정적인 코드를 작성할 수 있도록 돕는 중요한 기능입니다. Nullable과 Non-nullable 타입을 명확히 구분하고, 안전한 호출 연산자(?.
), 엘비스 연산자(?:
), 널 아님 단언(!!
) 등을 적절히 활용하여 코드를 작성하면, 널로 인한 오류를 최소화할 수 있습니다.
다음과 같은 널 처리 방식을 습득하고 사용하는 것이 코틀린의 기본이자, 코드를 더 안전하게 만드는 길입니다. 앞으로 Kotlin을 사용하면서 널 안전성을 염두에 두고 코드를 작성해 보세요. 널 안전성을 적절히 활용하면 유지보수와 디버깅이 쉬운, 더욱 견고한 소프트웨어를 개발할 수 있을 것입니다.