4.2.4 안전한 호출 연산자
널이 될 수 있는 타입의 값에 대해서는 그에 상응하는 널이 될 수 없는 타입의 값에 있는 메서드를 사용할 수 없다고 이미 설명했다. 하지만 특별한 안전한 호출 연산(safe call)을 사용하면 이런 제약을 피할 수 있다. 앞에서 본 예제를 다시 살펴보자.
fun readInt() = readLine()!!.toInt()
여러분의 프로그램이 콘솔을 표준 I/O로 사용하는 한 이 함수는 잘 작동한다. 하지만 프로그램이 파일을 표준 입력에 파이프(pipe)로 연결하면, 파일이 비어있는 경우 이 함수가 KotlinNull PointerException 예외를 발생시키면서 실패할 수 있다. 안전한 호출 연산자를 사용하면 다음 형태로 코드를 다시 작성할 수 있다.
fun readInt() = readLine()?.toInt()
앞의 코드는 기본적으로 다음 함수와 같다.
fun readInt(): Int? { val tmp = readLine() return if (tmp != null) tmp.toInt() else null }
즉, 안전한 호출 연산자는 수신 객체(왼쪽 피연산자)가 널이 아닌 경우 일반적인 함수 호출처럼 작동한다. 하지만 수신 객체가 널이면 안전한 호출 연산자는 호출을 수행하지 않고 그냥 널을 돌려준다. ||나 &&와 비슷하게 안전한 호출 연산도 지연 연산의 의미를 따른다. 다시 말해 수신 객체가 널이면 안전한 호출 연산자는 함수의 인자를 계산하지 않는다. 우선순위 면에서 ?. 연산자는 일반적인 함수 호출 연산자(.)와 같은 수준이다.