널 안전성
널 안전성 (Null safety)
Kotlin에서는 null 값을 가질 수 있어요. 무언가가 없거나 아직 설정되지 않았을 때 Kotlin은 null 값을 사용해요. 앞선 Collections 챕터에서 맵에 없는 키로 키-값 쌍에 접근하려 할 때 Kotlin이 null 값을 돌려주는 예를 이미 봤죠. 이렇게 null 값을 쓰는 게 유용하긴 하지만, 코드가 그런 경우를 처리할 준비가 안 되어 있으면 문제가 생길 수 있어요.
프로그램에서 null 값 때문에 생기는 문제를 막기 위해 Kotlin은 널 안전성(null safety)을 갖추고 있어요. 널 안전성은 null 값의 잠재적 문제를 실행 시점이 아니라 컴파일 시점에 잡아내요.
널 안전성은 다음을 할 수 있게 해 주는 기능들의 조합이에요.
- 프로그램에서
null값이 허용되는 시점을 명시적으로 선언해요. null값을 확인해요.null값일 수 있는 속성이나 함수에 안전 호출(safe call)을 사용해요.null값이 감지되면 수행할 동작을 선언해요.
출처: Kotlin 공식 문서
본문
널 가능 타입 (Nullable types)
Kotlin은 널 가능 타입(nullable types)을 지원해서, 선언된 타입이 null 값을 가질 수 있게 해 줘요. 기본적으로 타입은 null 값을 받아들이지 않아요. 널 가능 타입은 타입 선언 뒤에 ?를 명시적으로 붙여서 선언해요.
예를 들어 이렇게요.
fun main() {
// neverNull has String type
var neverNull: String = "This can't be null"
// Throws a compiler error
neverNull = null
// nullable has nullable String type
var nullable: String? = "You can keep a null here"
// This is OK
nullable = null
// By default, null values aren't accepted
var inferredNonNull = "The compiler assumes non-nullable"
// Throws a compiler error
inferredNonNull = null
// notNull doesn't accept null values
fun strLength(notNull: String): Int {
return notNull.length
}
println(strLength(neverNull)) // 18
println(strLength(nullable)) // Throws a compiler error
}
length는 문자열 안에 있는 문자의 개수를 담고 있는 String 클래스의 속성이에요.
널 값 확인하기 (Check for null values)
조건식 안에서 null 값의 존재를 확인할 수 있어요. 다음 예제에서 describeString() 함수에는 maybeString이 null이 아닌지, 그리고 그것의 length가 0보다 큰지 확인하는 if 문이 있어요.
fun describeString(maybeString: String?): String {
if (maybeString != null && maybeString.length > 0) {
return "String of length ${maybeString.length}"
} else {
return "Empty or null string"
}
}
fun main() {
val nullString: String? = null
println(describeString(nullString))
// Empty or null string
}
안전 호출 사용하기 (Use safe calls)
null 값을 가질 수 있는 객체의 속성에 안전하게 접근하려면 안전 호출 연산자 ?.를 사용해요. 안전 호출 연산자는 객체나 객체에서 접근한 속성 중 하나가 null이면 null을 돌려줘요. 코드에서 null 값 때문에 오류가 발생하는 걸 피하고 싶을 때 유용해요.
다음 예제에서 lengthString() 함수는 안전 호출을 사용해서 문자열의 길이 또는 null을 돌려줘요.
fun lengthString(maybeString: String?): Int? = maybeString?.length
fun main() {
val nullString: String? = null
println(lengthString(nullString))
// null
}
안전 호출은 연결(chaining)해서 쓸 수 있어요. 객체의 어떤 속성이 null을 포함하면 오류가 발생하지 않고 null이 돌아오죠. 예를 들어 이렇게요.
person.company?.address?.country
안전 호출 연산자는 확장 함수나 멤버 함수를 안전하게 호출하는 데도 쓸 수 있어요. 이 경우 함수를 호출하기 전에 널 검사를 수행해요. 검사에서 null 값이 감지되면 호출을 건너뛰고 null을 돌려줘요.
다음 예제에서 nullString은 null이라 .uppercase() 호출을 건너뛰고 null을 돌려줘요.
fun main() {
val nullString: String? = null
println(nullString?.uppercase())
// null
}
엘비스 연산자 사용하기 (Use Elvis operator)
null 값이 감지되면 돌려줄 기본값을 엘비스 연산자(Elvis operator) ?:로 제공할 수 있어요.
엘비스 연산자 왼쪽에는 null 값을 확인할 대상을 쓰고, 오른쪽에는 null 값이 감지되면 돌려줄 값을 써요.
다음 예제에서 nullString은 null이라서 length 속성에 접근하는 안전 호출이 null 값을 돌려줘요. 그 결과 엘비스 연산자가 0을 돌려주죠.
fun main() {
val nullString: String? = null
println(nullString?.length ?: 0)
// 0
}
Kotlin에서 널 안전성에 대한 더 자세한 내용은 Null safety 문서를 참고해요.
연습 (Practice)
연습문제 (Exercise)
회사의 직원 데이터베이스에 접근할 수 있게 해 주는 employeeById 함수가 있어요. 그런데 이 함수는 Employee? 타입의 값을 돌려주기 때문에, 결과가 null일 수 있어요. 목표는 id가 주어졌을 때 직원의 급여를 돌려주거나, 직원이 데이터베이스에 없으면 0을 돌려주는 함수를 작성하는 거예요.
data class Employee (val name: String, var salary: Int)
fun employeeById(id: Int) = when(id) {
1 -> Employee("Mary", 20)
2 -> null
3 -> Employee("John", 21)
else -> null
}
fun salaryById(id: Int) = // Write your code here
fun main() {
println((1..5).sumOf { id -> salaryById(id) })
}
data class Employee (val name: String, var salary: Int)
fun employeeById(id: Int) = when(id) {
1 -> Employee("Mary", 20)
2 -> null
3 -> Employee("John", 21)
else -> null
}
fun salaryById(id: Int) = employeeById(id)?.salary ?: 0
fun main() {
println((1..5).sumOf { id -> salaryById(id) })
}
다음 단계 (What's next?)
축하해요! 이제 입문 투어를 모두 마쳤어요. 중급 투어를 통해 Kotlin에 대한 이해를 한 단계 더 끌어올려 볼까요.