본문 바로가기

Kotlin

공식 문서로 배우는 코틀린 - 10. Types - Type checks and casts

열 번째, 타입 검사와 캐스트입니다.

 

코틀린에서는 특정 객체의 타입을 확인하기 위해 타입 검사를 할 수 있습니다. 타입 캐스트는 객체를 특정한 타입으로 변환하는 것을 말합니다.

특별히 제네릭의 타입 검사와 캐스트를 공부하고 싶은 경우에는 제네릭 타입 검사와 캐스트를 보시면 됩니다.

 

is 와 !is 연산자

특정 객체가 특정 타입인지 아닌지 식별하는 검사를 실행 시간에 수행할 때는 is 나 그 부정형인 !is 연산자를 사용합니다.

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // !(obj is String) 과 같습니다.
    print("Not a String")
} else {
    print(obj.length)
}

 

스마트 캐스트

코틀린 컴파일러는 is 검사나 불변 값을 위한 명시적 캐스트를 추적하고 필요한 경우 (안전한) 캐스트를 자동으로 추가하기 때문에, 대부분의 경우 명시적인 캐스트 연산자를 사용할 필요가 없습니다.

fun demo(x: Any) {
    if (x is String) {
        // 위의 x is String에서 String으로 확인된 경우이므로,
        // String으로 명시적으로 캐스트하지 않아도 자동으로 캐스트됩니다.
        print(x.length) 
    }
}

 

컴파일러는 부정형 타입 확인 반환하는 경우에 대해서도 하위에서 자동 캐스트 하는 것이 안전하다는 것을 알 정도로 영리합니다.

if (x !is String) return

print(x.length) // x is automatically cast to String

 

&& 나 !! 연산자에 대해서도 적절히 판단합니다.

// ||는 오른쪽 부분이 참이면 왼쪽 부분까지 평가하지 않지만
// 오른쪽 부분이 거짓이면, x가 String이라는 뜻이 되고
// 이를 컴파일러가 인지가 오른쪽 부분에서 x를 String으로 자동으로 캐스트합니다.
if (x !is String || x.length == 0) return

// &&은 왼쪽 부분이 참일 경우에만 오른쪽 부분을 평가하므로
// 오른쪽 부분을 평가한다는 것은 x가 String이라는 뜻이므로
// 컴파일러는 이를 인지하고, 오른쪽 부분에서 x를 String으로 자동 캐스트합니다.
if (x is String && x.length > 0) {
    print(x.length) // 여기에서 x는 String으로 자동 캐스트 됩니다.
}

 

또한, 스마트 캐스트는 when 표현식while 루프에서도 동작합니다.

when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}
스마트 캐스트는 컴파일러가 타입을 검사하는 때와 사용하는 시점 사이에 변수가 변경되지 않는다고 보장할 수 있을 때만 동작합니다.

 

스마트 캐스트는 다음과 같은 상황에서 사용될 수 있습니다.

val 지역 변수 지역 위임 프로퍼티를 제외하고 항상
val 프로퍼티 private/internal 프로퍼티거나 프로퍼티가 선언된 같은 모듈내에서 타입을 확인한 경우. 스마트 캐스트는 open 프로퍼티나 맞춤화된 게터가 있는 프로퍼티에는 동작하지 않음.
var local 변수 변수 검사 시점과 사용 시점에 값이 변경되지 않으며, 해당 변수의 값을 변경하는 람다에 캡처되지도 않고, 로컬 위임 프로퍼티가 아닌 경우.
var 프로퍼티 결코 적용되지 않음. var 프로퍼티는 다른 코드에 의해서 언제든지 변경될 수 있기 때문에 불가.

 

"안전하지 않은(Unsafe)" 캐스트 연산자

보통, 캐스트 연산자는 캐스트가 불가한 경우 예외를 던집니다. 그래서, 이를 안전하지 않다(unsafe)고합니다. 코틀린에서 안전하지 않은 캐스트는 as 중위 연산자로 수행됩니다.

unsafe는 '위험한'으로도 번역될 수 있습니다만, 위험이라는 단어를 쓰면 절대 써서는 안 될 거 같은 느낌이 있어서 '안전하지 않은'으로 번역했습니다. 실무에서는 그냥 '언세이프'로 바로 얘기하는 경우도 많을 것으로 생각됩니다.
val x: String = y as String

 

String 타입은 널 가능하지 않기 때문에 null은 String으로 캐스트 될 수 없습니다. y가 null이면, 위에 있는 코드는 예외를 던집니다. 이러한 코드를 null 값이 캐스트 되도록 수정하려면, 캐스트의 오른쪽 부분에 널 가능한 타입을 사용합니다(당연히 왼쪽 부분에도 사용합니다).

val x: String? = y as String?

 

"안전한" (널 가능한) 캐스트 연산자

예외를 피하기 위해서, 캐스트에 실패하는 경우 null을 반환하는 안전한 캐스트 연산자인 as?를 사용합니다. 

val x: String? = y as? String

 

as?의 오른쪽 부분은 널 가능한 타입이 아니지만 캐스트에 실패하는 경우 null을 반환하므로 캐스트 결과는 널 가능합니다.