Kotlin in Acion 이어서 타입 공부!
코틀린과 자바의 가장 중요한 차이는 코틀린에선 널이 될 수 있는 타입을 명시적으로 지원한다는 점이다.
Type?
= Type or null
Nullable
어떤 타입이든 타입 이름 뒤에 ?
를 붙이면 그 타입의 변수나 프로퍼티에 null 참조를 저장할 수 있다.
즉, 물음표가 없는 타입은 기본적으로 null이 될 수 없다.
널이 될 수 있는 타입의 변수가 있으면 그에 대해 수행할 수 있는 연산이 제한된다. ?가 달린 타입의 변수에 대해 변수.메서드()처럼 직접 호출할 수 없다. 직접 호출 시 컴파일러가 컴파일 과정에서 에러를 뱉는다.
단, null과의 비교를 하고 나면 컴파일러가 그것을 기억해 원래처럼 사용할 수 있다.
fun strLenSafe(s: String?): Int = if(s!=null) s.length else 0
?.
를 사용하면 해당 변수나 객체가 null이 아닌 경우에만 접근하고 그렇지 않은 경우에는 null을 반환한다.
fun printAllCaps(s: String?) {
val allCaps: String? = s?.toUpperCase()
println(allCaps)
}
fun main(args: Array) {
printAllCaps("abc") // ABC
printAllCaps(null) // null
}
null 일 때 디폴트 값을 정의하는 데 사용된다.
변수나 표현식이 null이 아닌 경우에는 해당 값을 사용하고, null인 경우에는 우측에 있는 값으로 대체된다.
fun getName(str: String?) {
val name = str ?: "Unknown"
}
// 파라미터로 들어오는 인자가 null이면 "Unknown"으로 저장
식
이다.as?는 안전한 형 변환을 수행하는 데 사용된다. 형 변환할 수 있는지 불확실한 상황에서 사용된다.
이 연산자를 사용하면 형 변환이 실패할 경우 예외가 발생하는 대신 null을 반환한다.
val x: String? = "Hello"
val y: Int? = x as? Int
// x가 nullable하게 선언되어 있으므로 Int?로 형변환 가능
!!는 변수나 표현식이 null이 아님이 분명할 때 사용되며, 해당 값이 null일 경우 예외를 발생시킨다.
null이 발생하면 오히려 NPE를 던진다.
fun ignoreNulls(s: String) {
val sNotNull: String = s!!
println(sNotNull.length)
}
Kotlin 표준 라이브러리에서 제공하는 함수 중 하나로, 주어진 블록을 수신 객체로 호출한다.
일반적으로 let은 null이 아닌 객체에서 작업을 수행하거나, 어떤 값을 다른 형태로 변환할 때 사용된다.
inline fun <T, R> T.let(block: (T) -> R): R
val nullableString: String? = "Hello, Kotlin"
nullableString?.let { nonNullString ->
println(nonNullString.length) // 수신객체가 null이 아닌 경우에만 실행된다. null이면 아무 작업도 안함
}
lateinit은 변수를 선언 시 바로 초기화 하지 않고, 나중에 필요한 시점에 초기화하도록 도와준다.
DI를 쓰는 프레임워크에서 자주 사용한다.
class Example {
lateinit var name: String
}
val은 당연히 사용 불가하고 var에서만 사용 가능하다.
정수형(Int, Long, Short, Byte), 실수형(Double, Float), 불린(Boolean) 타입에는 사용할 수 없다.
초기화 전에 사용시 NPE가 발생하는게 아니라, 'UninitializedPropertyAccessException'가 발생한다.
프로퍼티가 선언된 시점에서 not null로 간주되기 때문에 nullable이 아니다.
초기화는 단 한번만 가능하다.
코틀린에서 함수나 클래스의 모든 타입 파라미터(T)는 기본적으로 null이 될 수 있다.
그래서 T를 크랠스나 함수 안에서 타입이름으로 사용하면 ?가 없어도 nullable한 타입이다.
T는 Any?타입으로 추론되므로 안전한 호출(?.)을 사용해야 한다.
fun <T> printHashCode(t:T) {
println(t?.hashCode())
}
fun <T:Any> printHashCodeNotNull(t:T) {
println(t?.hashCode())
}
플랫폼 타입은 코틀린이 null 관련 정보를 알 수 없는 타입이다.
이 타입은 nullable로 처리해도 되고, not-null 타입으로 처리해도 되지만 책임은 개발자가 진다.
자바코드를 코틀린에서 다루게 될 경우 자바에선 type의 null 가능성에 대한 명시가 없는 경우가 있다.(@NotNull 같은 애너테이션 제외) 이때 자바의 type은 플랫폼 타입 취급을 받느다.
자바의 Type은 코틀린에서 Type이든 Type?로 처리하든 개발자가 선택하기에 따라 달려있다.
모든 타입을 nullable로 처리하면 검사에 시간과 자원이 소모된다. 특히 자바에서 ArrayList<String>을 ArrayList<String?>처럼 코틀린에서 다루면 매 원소마다 널 검사를 수행해서 검사에 드는 비용이 커진다.
그러므로 자바API를 다룰 땐 NPE가능성과 성능을 모두 염두해 두고 null 처리를 해야 한다.