코틀린 언어 용어 사전
널 안전smart cast

스마트 캐스트

검사하면 컴파일러가 기억한다. 다만 '바뀔 수 있는 값' 은 기억해 주지 않는다.

타입을 검사하고 나면 그 블록 안에서 컴파일러가 캐스팅을 대신 해 준다.

fun describe(x: Any): String {
    if (x is String) return x.length.toString()   // x 를 String 으로 쓴다 — 캐스팅 없이
    if (x != null) { /* 여기서 x 는 non-null */ }
    return "?"
}

자바라면 if (x instanceof String) { ((String) x).length(); } 처럼 방금 확인한 것을 다시 적어야 한다. 검사와 캐스팅이 따로 놀면 둘이 어긋날 여지가 생기는데, 스마트 캐스트는 그 여지를 없앤다.

널 검사도 같은 원리다. if (x != null) 이후로 x 는 non-null 타입으로 취급되어 ?. 없이 쓸 수 있다.

바뀔 수 있는 값은 안 해 준다

여기가 실제로 걸리는 지점이다.

class Foo { var name: String? = null }

fun f(foo: Foo) {
    if (foo.name != null) {
        println(foo.name.length)   // 컴파일 에러
    }
}

var 프로퍼티는 검사한 다음 줄에서 다른 스레드가 바꿀 수 있다. 검사 시점의 결과를 믿을 근거가 없으므로 컴파일러가 거부한다. 안전을 위한 거절이지 문법의 결함이 아니다.

같은 이유로 다음도 안 된다.

  • 커스텀 게터가 있는 프로퍼티 — 부를 때마다 다른 값을 줄 수 있다
  • 다른 모듈의 open 프로퍼티 — 하위 클래스가 게터를 재정의했을 수 있다

지역 변수로 받으면 된다

val n = foo.name              // 값을 붙잡는다
if (n != null) println(n.length)   // val 이라 안 바뀐다 → 스마트 캐스트 성립

foo.name?.let { println(it.length) }   // 관용적인 방법

val 지역 변수는 한 번 정해지면 바뀌지 않으므로 컴파일러가 안심하고 기억한다. ?.let 은 같은 일을 한 줄로 하는 방식이다 — 값이 있을 때만 블록이 돌고, 그 안에서 it 은 non-null 이다.

조건 안에서도 이어진다

if (x is String && x.length > 3) { ... }    // && 오른쪽에서 이미 String
if (x !is String || x.length > 3) { ... }   // || 오른쪽에서도 성립

&& 는 왼쪽이 참일 때만 오른쪽을 평가하므로, 오른쪽에서는 검사가 이미 통과한 상태다. 컴파일러가 그 흐름을 따라간다. || 는 왼쪽이 거짓일 때만 오른쪽으로 가므로 !is 의 부정, 즉 "String 이다" 가 성립한다.

이 흐름 추적을 라이브러리 함수까지 넓히는 장치가 계약(contract) 이다. isNullOrEmpty() 를 통과한 뒤 non-null 로 취급되는 것이 그 덕분이다 — 함수가 "false 를 반환했다면 인자는 null 이 아니다" 를 컴파일러에게 알려 준다.

when 과 함께 쓸 때가 진가다

sealed interface Result
data class Ok(val value: String) : Result
data class Err(val code: Int) : Result

fun handle(r: Result) = when (r) {
    is Ok  -> r.value        // Ok 로 스마트 캐스트
    is Err -> "E${r.code}"    // Err 로 스마트 캐스트
}

sealed 로 하위 타입이 고정되어 있으면 컴파일러가 가짓수를 다 다뤘는지까지 검사한다. 새 하위 타입을 추가했을 때 처리를 빠뜨린 when 이 컴파일 시점에 드러난다.

면접 함정

  • "스마트 캐스트가 안 되는 건 코틀린의 한계"var 는 검사 후 바뀔 수 있으므로 거부가 맞다.
  • "!! 로 넘기면 된다" → 검사를 했는데 !! 를 쓴다는 것은 컴파일러의 경고를 무시하는 것이다. 지역 val 이나 ?.let 으로 푼다.
  • "as 캐스팅과 같다"as 는 실패하면 예외다. 스마트 캐스트는 검사가 이미 끝난 뒤라 실패할 수 없다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 널 안전 — ?·!!·Elvis·플랫폼 타입·스마트 캐스트