본문 바로가기

Dev Book/Kotlin IN ACTION

CH6(6.1-1). 널 가능성

1. 널이 될 수 있는 타입

- 코틀린과 자바의 가장 중요한 차이는 코틀린 타입 시스템이 널이 될 수 있는 타입을 지원한다는 점이다. 자바에서는, 어떤 변수가 널이 될 수 있다면 그 변수에 대해 메소드를 호출할 시 NullPointerException이 발생할 수 있으므로 안전하지 않다. 코틀린은 그런 메소드 호출을 금지함으로써 많은 오류를 방지한다. (NPE의 경우 런타임 시 발생하지만, 코틀린에서는 컴파일 시 오류를 발생시킨다.)

- strLen 함수의 경우 파라미터 s의 타입인 String이 널로 넘어오지 못하게 컴파일 오류를 발생시킨다. 이 함수가 널과 문자열을 인자로 받을 수 있게 하려면 타입 이름 뒤에 물음표(?)를 명시해야 한다.

 

fun strLenSafe(s:String?) = ...

 

type? = type 또는 null. 물음표가 없는 타입은 그 변수가 null 참조를 저장할 수 없다는 뜻이다. 따라서 모든 타입은 기본적으로 널이 될 수 없는 타입이다.

- null이 될 수 있는 타입의 변수는 그에 대해 수행할 수 있는 연산이 제한된다. 예를들어, 변수.메소드()처럼 메소드를 직접 호출할 수 없다.

- null이 될 수 있는 타입의 가장 중요한 일은 바로 null과 비교하는 것이다. 일단 null과 비교한 후, 컴파일러는 그 사실을 기억하고 null이 아님이 확실한 영역에서는 해당 값을 널이 될 수 없는 타입의 값처럼 사용할 수 있다.

 

fun strLenSafe(s: String?) : Int = 
	if(s!=null) s.length else 0

 

2. 안전한 호출 연산자: ?.

- ?.은 null 검사와 메소드 호출을 한 번의 연산으로 수행한다. 호출하려는 값이 null이 아니라면 ?.은 일반 메소드 호출처럼 작동한다. 호출하려는 값이 null이면 이 호출은 무시되고 null이 결과 값이 된다.

 

class Employee(val name:String, val manager: Employee?)

fun managerName(employee: Employee): String? = employee.manager?.name

>>> val ceo = Employee("boss", null)
>>> val developer = Employee("bob", ceo)
>>> println(managerName(developer)
boss
>>>println(managerName(ceo))
null

 

3. 엘비스 연산자: ?:

- 코틀린은 null 대신 사용할 디폴트 값을 지정할 때 편리하게 사용할 수 있는 연산자를 제공한다. 이를 엘비스 연산자라고 한다.

 

fun strLenSafe(s: String?): Int = s?.length?:0

>>>println(strLenSafe("abc")
3
>>>println(strLenSafe(null))
0

 

4. 안전한 캐스트: as?

- 자바 타입 캐스트와 마찬가지로 대상 값을 as로 지정한 타입으로 바꿀 수 없으면 ClassCastException이 발생한다. as를 사용할 때마다 is로 미리 변환 가능한지 검사할 수 있으나, as? 연산자를 이용해 어떤 값을 지정한 타입으로 캐스트 하되, 대상 타입으로 변환할 수 없으면 null을 반환하도록 할 수 있다.

 

class Person(val firstName:String, val lastName: String){
	override fun equals(o:Any?): Boolean{
    	val otherPerson = o as? Person ?: return false
    return otherPerson.firstName == firstName &&
    		otherPerson.lastName == lastName
    }
    
    override fun hashCode():Int = 
    	firstName.hashCode()*37+lastName.hashCode()
}

 

5. 널 아님 단언: !!

- !!를 사용하면 어떤 값이든 널이 될 수 없는 타입으로 바꿀 수 있다. 실제 널에 대해 !!를 적용하면 NPE가 발생한다.

 

fun ignoreNulls(s:String?){
	val sNotNull:String= s!!
    println(sNotNull.length)

 

- 어떤 함수가 값이 널인지 검사한 다음, 다른 함수를 호출한다고 해도 컴파일러는 호출된 함수 안에서 그 값을 사용할 수 있음을 인식할 수 없다. 하지만 이런 경우 호출된 함수가 언제나 다른 함수에서 널이 아닌 값을 전달받는다는 사실이 분명하다면 굳이 널 검사를 다시 수행하고 싶지 않을 것이다. 이 경우 널 아님 단언문을 쓸 수 있다.

6. let 함수

- let 함수를 안전한 호출 연산자와 함께 사용하면 원하는 식을 평가해서 결과가 널인지 검사한 다음에 그 결과를 변수에 넣는 작업을 간단한 식을 사용해 한꺼번에 처리할 수 있다.

 

fun sendEmailTo(email:String){
	println("Sending email to $email")
}

>>>val email:String? = "yellow@example.com"
>>>email?.let {sendEmailTo(it)}
Sending email to yellow@example.com
>>>email = nul
>>>email?.let {sendEmailTo(it)}