최근 ``kt null``에 대한 접근 방법은 런타임에 발생하는 NPE를, 컴파일 타임으로 옮기는 것이다.
널이 될 수 있음과 없음에 대한 모든 검사는 컴파일 타임에 수행되기 때문에, 실행 시점에는 널이 될 수 있는 타입과 널이 될 수 없는 타입의 객체가 같아진다.
단, ``kt @NotNull`` 애너테이션이나
``java Intrinsics.checkExpressionValueIsNotNull()``
``java Intrinsics.checkParameterIsNotNull()``같은 체크가 추가된다.
그리고 또 한 가지 중요한 것이, 모든 검사가 컴파일 타임에 수행되기 때문에 런타임에 가져오는 데이터에 대해서는 Exception이 발생할 수 있다.
따라서 런타임에 가져오는 데이터가 널이 될 수 있는지, 없는지를 조금 더 신경써서 생각해야 한다. 널이 될 수 있는 타입으로 지정해 놓으면 ``kt ?.`` 등의 널 처리 연산자를 이용해 간편하게 처리할 수 있다.
런타임에 ``kt null``을 가져와 널이 될 수 없는 타입에 집어 넣거나, ``kt !!``를 잘못 사용하는 경우 프로그램 실행 도중 다음 예외가 발생하며 종료될 수 있다.
- ``java java.lang.IllegalStateException: ??? must not be null``
- ``kt kotlin.TypeCastException: null cannot be cast to non-null type``
그러나 여기서 또 중요한게... 분명 널이 될 수 없는 타입인데 외부 데이터를 가져오면서 여기에 ``kt null``이 들어갔음에도 예외가 발생하지 않는 경우가 있으므로 주의해야 한다. 예를 들어 ``kt Gson()``은 인스턴스화에 Java reflection을 사용하는데, 이러한 경우 코틀린이 이러한 프로세스에 관여할 수 있는 방법이 없으므로 널이 될 수 없는 타입을 ``kt null``로 초기화해도 예외가 발생하지 않는다. 이런 경우 객체 초기화 시점에서가 아니라 멤버에 접근하는 시점에서 ``java java.lang.NullPointerException``이 발생하게 된다.
안전한 호출 연산자 ?. / 엘비스 연산자 ?:
```kt
s?.toUpperCase() // 다음 구문과 동일
if (s != null) s.toUpperCase() else null // s==null이면 결과 타입도 null이 된다는 점 유의.
```
연쇄해서 사용한다면 코드를 굉장히 줄일 수 있다.
엘비스 연산자 ``kt ?:``와 연계해서 사용하면 이런 모습이다.
```kt
class Address(val city: String, val country: String)
class Company(val name: String, val address: Address?)
class Person(val name: String, val company: Company?)
fun Person.countryName(): String =
this.company?.address?.country ?: "Unknown"
>>> val p = Person("umbum", null)
>>> println(p.countryName())
```
코틀린에서는 ``kt return, throw``등의 연산도 식이다. 따라서 엘비스 연산자의 우항에 넣을 수 있다.
또는, ``kt Nothing`` 타입 함수를 엘비스 연산자의 우항에 넣고 사용하면 좋다.
```kt
val address = person.company?.address ?: throw IllegalArgumentException("No address")
```
안전한 캐스트 as?
널 아님 단언 !!
- ``kt var``이거나, 커스텀 접근자를 사용하는 프로퍼티에 대한 null check 수행 이후. (스마트 캐스트가 동작하지 않음)
- 액션 API에서 액션이 호출되었을 때, 어떤 값이 필연적으로 존재해야만 이 액션이 호출될 수 있는 경우. 즉, 그 값이 ``kt null``일 수가 없음이 자명한 경우.
var 지연 초기화 : lateinit
val 지연 초기화 : lazy
널이 될 수 있는 타입 확장
플랫폼 타입
자바의 타입은 코틀린에서 플랫폼 타입, 즉 널 관련 정보를 알 수 없는 타입이 된다.
단, 자바 원시 타입의 값은 널이 될 수 없으므로 널이 될 수 없는 타입으로 취급된다.
플랫폼 타입은 널이 될 수 있는 타입으로 처리해도 되고 널이 될 수 없는 타입으로 처리해도 되기 때문에, 이 값이 널이 될 수 있는지 없는지 잘 생각해보고 널이 될 수 없다면 그냥 사용하면 되고, 널이 될 수 있다면 널 체크를 수행해주어야 한다.
아무튼, 자바 API를 사용할 때는 널을 반환할지 아닐지를 잘 생각해보아야 한다.
```kt
>>> val p = JavaPerson("umbum")
>>> println(p.name.capitalize())
Umbum
>>> val np = JavaPerson(null)
>>> println(np.name.capitalize())
java.lang.IllegalStateException: np.name must not be null
```
플랫폼 타입은 ``kt Type!``으로 표시된다. 직접 선언할 수는 없고 자바에서 가져온 것만 이렇게 표기된다.
플랫폼 타입은 널이 될 수 있는 타입이 될 수도, 널이 될 수 없는 타입이 될 수도 있기 때문에 둘 다 대입할 수 있다.
```kt
>>> val i: Int = np.name
error: type mismatch: inferred type is String! but Int was expected
>>> val kp: String? = p.name
>>> val kp: String = p.name
```
그러나, 이미 ``kt null``이 들어 있는 상태에서 널이 될 수 없는 타입에 대입하려고 하면 Exception이 발생한다.
```kt
>>> val kp: String? = np.name
>>> val kp: String = np.name
java.lang.IllegalStateException: np.name must not be null
```
아무튼, 그냥 널이 될 수 있는지 없는지만 신경쓰면 된다.
자바 클래스나 인터페이스를 코틀린에서 상속 또는 구현하는 경우에도 널이 될 수 있는지 없는지만 신경써서 붙여주면, 나머지는 컴파일러가 알아서 해준다.
'Languages & Frameworks > Kotlin' 카테고리의 다른 글
[Kotlin] 컬렉션과 배열 (0) | 2017.12.07 |
---|---|
[Kotlin] 타입 시스템 (0) | 2017.12.06 |
[Kotlin] Nullability 관련 연산자, lateinit, lazy (0) | 2017.12.06 |
[Kotlin] 수신 객체 지정 람다 : with / apply / let / run / takeif / also (1) | 2017.12.05 |
[Kotlin] 함수형 인터페이스(SAM)에 람다 사용하기 (2) | 2017.12.05 |
[Kotlin] filter/map, any/all/count/find, groupBy, flatMap, 시퀀스(Sequence) (0) | 2017.12.04 |