널 허용(nullable) 타입은 안전 호출 연산자(?.)나 엘비스 연산자(?:)와 결합해서 사용한다.
// Person 클래스 classPerson(val first: String,val middel: String?,val last: String)
middle 값에 따라서 middleNameLength 값을 채우려고 할 때, 자바에 익숙하다면 우리는 다음과 같이 작성할 것이다.
val p =Person(first ="north", middle =null, last ="West")if (p.middle !=null) {val middleNameLength = p.middle.length}
그런데 만약 p가 val 대신 var을 사용한다면 변수 p가 정의된 시점과 p의 middle 속성에 접근하는 시점 중간에 값이 변경되었을 수도 있다고 가정한다면 위와 같은 코드는 영리한 타입 변환을 수행하지 않는다. 이를 우회하는 한 가지 방법은 널 아님 단언 연산자라 부르는 거듭 느낌표(!!)를 사용하는 것이다.
val middleNameLength = p.middle!!.length
그러나 널 아님 단언 연산자 !!가 하나라도 있다면 이는 코드 스멜이다. 널 값에 !! 연산자를 사용하는 것은 코틀린에서 NullPointerException을 만날 수 있는 몇 가지 상황 중 하나이므로, 가능하면 사용하지 않도록 노력하자.
안전 호출 연산자 사용하기
val p =Person(first ="north", middle =null, last ="West")val middleNameLength = p.middle?.length
middleNameLength의 타입은 아마 사용하고자 했던 타입이 아니라 Int? 타입이므로 다음과 같이 안전 호출 연산자와 엘비스 연산자(?:)를 병행해서 사용하는 것이 유용하다.
안전 호출 연산자와 엘비스 연산자
val p =Person(first ="north", middle =null, last ="West")val middleNameLength = p.middle?.length ?: 0
안전 타입 변환 연산자
안전 타입 변환 연산자의 목적은 타입 변환이 올바르게 동작하지 않는 경우 ClassCastException이 발생하는 상황을 방지하는 것이다. 예를 들어 어떤 인스턴스를 Person 타입으로 변환을 시도했지만 해당 인스턴스가 널일 수도 있는 상황이라면 다음과 같이 작성할 수 있다.
val p1 = p as? Person
자바를 위한 메소드 중복
기본 파라미터를 가진 코틀린 함수가 있는데, 자바에서 각 파라미터의 값을 직접적으로 명시하지 않고 해당 코틀린 함수를 호출하고 싶다면 @JvmOverloads 애노테이션을 해당 함수에 추가해라.
funaddProduct(name: String, price: Doule=0.0, desc: String? =null) ="Adding product with $name, ${desc ?: "None"}, and "+ NumberFormat.getCurrencyInstance().format(price)
addProduct 함수는 문자열 name이 필수지만 description과 price는 기본값이 있다. description에 널을 할당할 수 있고 기본값도 널인 반면 price의 기본값은 0이다. 그러므로 1개 또는 2개나 3개의 인자와 함께 addProduct 함수를 쉽게 호출할 수 있다.
@Testfun`check all overloads`() {assertAll("Overloads called from Kotlin", { println(addProduct("Name", 5.0, "Desc")) }, { println(addProduct("Name", 5.0)) }, { println(addProduct("Name")) }, )}
Optional이나 널 허용 속성은 함수 시그니처의 마지막에 위치시켜야 한다. 그렇게 해야만 위치 인자를 사용해서 함수를 호출할 때 Optional 또는 널 허용 속성을 생략할 수 있다.
하지만 자바는 메소드 기본 인자를 지원하지 않기 때문에 자바에서 addProduct 함수를 호출하려면 파라미터를 다 채워야 한다.