Kotlin 타입체크와 캐스팅

김성준·2022년 4월 14일
0

Kotlin

목록 보기
2/17

is와 !is 연산자

(object) is (type)
(object) !is (type)

is연산자와 부정형인 !is연산자는 주어진 객체가 주어진 타입에 속하는지 여부를 나타내는 값을 반환합니다.

val num = 5

println(num is Int) // 출력: true

스마트 캐스트(Smart Cast)

대부분의 경우, 코틀린에서는 immutable한 값에 대해 명시적 형변환을 직접 해줄 필요가 없습니다. 컴파일러가 is 타입검사를 추적하고 형변환이 필요하면 자동으로 안전한 형변환을 삽입해주기 때문입니다.

fun smartCast(arg: Any?) {
    arg?:return
    if (arg is String) // is checked
        println("$arg.length is ${arg.length}") // arg is automatically cast to String
    else if (arg is Int)
        println("${arg + 4}") // arg is automatically cast to Int
}

fun main() {
    smartCast(5)
    smartCast("abc")
    smartCast(null)
}

컴파일러는 !is 연산도 추적하여 스마트 컴파일을 실행합니다.

fun smartCast(arg: Any?) {
    arg?:return
    if (arg !is String) // !is checked
        return
    else
        println("$arg.length is ${arg.length}") // arg is automatically cast to String
}

fun main() {
    smartCast("abc")
    smartCast(null)
}

논리연산과 함께 쓰는 is 연산

또는 &&, ||과 같은 논리연산에서 이뤄진 is 연산도 추적할 수 있습니다.

fun smartCast(arg: Any?) {
    arg?:return
    if (arg !is String || arg.length == 0) // !is checked
        return
    if (arg is String && arg.length > 0)
        println("$arg.length is ${arg.length}") // arg is automatically cast to String
}

fun main() {
    smartCast("abc")
    smartCast("")
}

when, while과 함께하는 is연산

스마트 캐스트는 when과 while에서도 적용됩니다.

fun smartCastWithWhen(arg: Any?) {
    arg?:return
    when (arg) {
        is Int -> println(arg + 1) // smartcast
        is String -> println(arg.length + 1) // smartcast
        is IntArray -> println(arg.sum()) // smartcast
    }

}

fun smartCastWithWhile(arg: Any?) {
    var idx = 0
    while (arg is IntArray && idx < arg.size) {
        print(arg[idx])
        idx++
    }
}

fun main() {
    smartCast("abc")
    smartCast(10)
    smartCast(intArrayOf(1, 2, 3))
}

[출력]
4
11
6
1234

스마트 캐스트의 제한조건

스마트 캐스트는 is 검사와 변수의 사용 사이에 변수가 변하지 않는다고 컴파일러가 확신할때만 실행됩니다. 보다 구체적인 조건은 아래와 같은 상황들입니다.

  • val 지역변수 : 로컬 위임 프로퍼티(local deligated property)를 제외하고 항상 가능합니다.

  • val 프로퍼티 : 프로퍼티가 private 또는 internal이거나 속성이 선언된 모듈 내에서 검사되는 경우엔 스마트 캐스트를 사용할 수 있습니다. 하지만 프로퍼티가 open이거나 사용자 정의 게터를 가지고 있는 경우에는 사용할 수 없습니다.

  • var 지역변수 : 변수가 검사와 사용 사이에 변경되지 않을때, 그 변수가 람다함수에서 수정되지 않을때, 로컬 위임 프로퍼티(local deligated property)가 아닐 때 스마트 캐스트를 사용할 수 있습니다.

  • var 프로퍼티 : 다른 코드에 의해 언제든 수정될 수 있기때문에 스마트 캐스트를 사용할 수 없습니다.

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

일반적으로 캐스트 연산자는 캐스트가 가능하지 않은 경우 예외를 throw합니다. 그래서 안전하지 않다고 합니다. Kotlin의 안전하지 않은 캐스트는 중위 연산자 as에 의해 수행됩니다.

val x: String = y as String

변환하려는 타입이 nullable이 아니므로 null을 String으로 캐스팅할 수 없습니다. y가 null이면 위의 코드에서 예외가 발생합니다. null 값에 대해 이와 같은 코드를 올바르게 만들려면 캐스트의 오른쪽에 nullable 형식을 사용하십시오.

val x: String? = y as String?

(안전한) 캐스트 연산자 as?

예외가 throw 되는 상황을 피하려면 안전한 캐스트 연산자인 as?을 사용해야 합니다. as?은 캐스트가 실패하면 null을 반환합니다.

val y = 5
val x: String? = y as? String

println(x) // 출력: null

타입 삭제와 제네릭 타입 체크
(Type erasure and generic type checks)

코틀린은 컴파일 타임에 제네릭을 포함하는 연산에 대한 안전을 보장합니다. 반면 런타임에 제네릭 타입의 인스턴스는 그들의 실제 타입에 대한 정보를 가지고 있지 않습니다. 예를 들어, List<foo>는 List<*>처럼 타입 정보가 지워집니다. 일반적으로, 인스턴스가 런타임에 특정 형식 인수를 가진 제네릭 형식에 속하는지 확인할 수 있는 방법은 없습니다.

이 때문에 컴파일러는 ints is List<Int> 또는 list is T(유형 매개변수)와 같이 타입 삭제로 인해 런타임에 수행할 수 없는 is-checks를 금지합니다. 그러나 * 투영 유형를 사용하여 is-checks를 수행할 수 있습니다.

fun checkStarProjectedType(arg: Any?) {
    arg ?: return

    if (arg is List<*>) {
        arg.forEach { println(it) } // The items are typed as `Any?`
    }
}

fun main() {
    val intLst = listOf(1, 2, 3)
    val strLst = listOf("abc", "cde", "fgh")

    checkStarProjectedType(intLst)
    checkStarProjectedType(strLst)
}

[출력]
1
2
3
abc
cde
fgh

인스턴스가 제네릭의 인자로 타입이 명시된 경우, 제네릭 타입을 제외한 구체적으로 구현된 객체에 대한 is-check는 가능합니다. 이 경우 제네릭 표현(ex.<Int>)은 생략 될 수 있습니다.

fun smartcastWithStaticGeneric(list: List<Int>?) {
// List<int> -> 제네릭의 타입이 명시되어 있음
    list ?: return

    if (list is ArrayList) {
    // 따라서 제네릭을 제외한 객체인 ArrayList에 대해 is-check가능
    // ArrayList<String..etc>는 불가능
        list.forEach { println(it) }
        // `list` is smart-cast to `ArrayList<String>`
    } else {
        println("this is not ArrayList")
    }
}

fun main() {
    val intLst = listOf(1, 2, 3)

    smartcastWithStaticGeneric(intLst)
}

reified type parameters를 갖는 인라인 함수는 각 호출되는 장소에서 인라인 된 실제 인수를 가지고 있습니다. 이것은 타입 파라미터에 대한 arg is T 검사를 가능하게 만듭니다. 하지만 만약 arg가 제네릭 타입 그 자체라면, arg의 타입은 여전히 지워져있습니다.

inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
    if (first !is A || second !is B) return null
    return first as A to second as B
}

val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)


val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // Compiles but breaks type safety!
// Expand the sample for more details

Unchecked Cast

추가 예정...

출처

코틀린 공식 문서

profile
수신제가치국평천하

0개의 댓글

관련 채용 정보