[kotlin] kotlin 2.0의 smart cast

할렐루카·2024년 6월 8일
0

kotlin진심펀치

목록 보기
6/6

kotlin 2.0.0에서 스마트캐스트가 더 똑똑해졌다고 한다. 한번 살펴보자

지역 변수로 스마트캐스트

class Cat {
    fun purr() {
        println("Purr purr")
    }
}

// 기존의 방법
fun petAnimal(animal: Any) {
    if (animal is Cat) {
        animal.purr()
    }
}

fun petAnimal(animal: Any) {
    val isCat = animal is Cat // 타입 체크 변수로 선언
    if (isCat) {
        animal.purr() // 2.0 이전 버전에선 타입 캐스트가 되지 않아 호출할 수 없다!
    }
}

fun main() {
    val kitty = Cat()
    petAnimal(kitty)
}

타입 체크를 지역 변수로 선언하면 if 조건문 밖이라도 해당 변수에 접근하여 자동으로 형변환을 해줄 수 있다.

논리 연산자 스마트캐스트

interface Status {
    fun signal() {}
}

interface Ok : Status
interface Postponed : Status
interface Declined : Status

fun signalCheck(signalStatus: Any) {
    if (signalStatus is Postponed || signalStatus is Declined) {
        // signalStatus는 공통 supertype인 Status로 스마트캐스트된다
        signalStatus.signal()
        // 2.0.0 이전 버전에서는 Any 타입으로 변환된다
        if(signalStatus is Status) signalStatus.signal() // 그래서 타입 체크 후에 signal() 호출해야 함
    }
}

inline fun 스마트캐스트

  • kotlin 2.0.0부터는 컴파일러가 local, inline과 같이 외부에서 참조가 불가능한 위치인 것을 확인하면 스마트캐스트를 해준다.
interface Processor {
    fun process()
}

inline fun inlineAction(f: () -> Unit) = f()

fun nextProcessor(): Processor? = null

fun runProcessor(): Processor? {
    var processor: Processor? = null
    inlineAction {
        // kotlin 2.0.0에서는 컴파일러가 processor 변수와 inlineAction()이 각각 지역변수와 inline fun인것을 인지하고 있다.
        // 따라서 heap에 객체가 생성되지 않기 때문에 외부로 노출이 안된다는 것을 인지할 수 있다
        // 그런 이유로 processor가 null이 아니면 스마트캐스트 해버린다
        if (processor != null) {
            processor.process()
            processor?.process() // kotlin 2.0.0 이전 버전에서는 safe call이 필요하다
        }

        processor = nextProcessor()
    }

    return processor
}

fun 타입의 프로퍼티

  • 기존의 kotlin 버전에서 fun 타입의 클래스 프로퍼티가 스마트캐스트되지 않던 버그가 수정되었다고 한다.
class Holder(val provider: (() -> Unit)?) {
    fun process() {
        // kotlin 2.0.0.에서 provider가 null이 아니라면 스마트캐스트된다
        if (provider != null) {
            provider()
            
            // kotlin 2.0.0 이전 버전에서는 다음과 같이 safe call이 필요하다
            provider?.invoke()
        }
    }
}
  • invoke()를 오버로드한 경우에도 적용된다고 한다.

에러핸들링

  • 객체를 추적하여 nullable한 타입인지 확인하고 catch와 finally 블록으로 넘겨주도록 컴파일러가 진화했다고 한다.
fun testString() {
    var stringInput: String? = null
    stringInput = "" // 이제부터 stringInput는 String 타입으로 스마트캐스트된다
    try {
        // 컴파일러가 stringInput이 null이 아닌 것을 안다
        println(stringInput.length)

        stringInput = null // 컴파일러가 이전의 스마트캐스트를 거절하고 다시 nullable한 String? 타입으로 취급한다

        // 예외 발생, 현재 String? 타입이다
        if (2 > 1) throw Exception()
        stringInput = ""
    } catch (exception: Exception) {
        // kotlin 2.0.0에서는 stringInput을 String?으로 인식하기 때문에 safe call이 필요하다
        println(stringInput?.length)
        
        // kotlin 2.0.0 이전 버전에서는 safe call이 필요하지 않지만 이것은 잘못됐다는 것을 우리는 알고 있다
    }
}

증감 연산자 이후의 스마트캐스트

  • kotlin 2.0.0에서는 기존에 증감 연산자 이후에 객체의 타입을 추적하지 못했던 문제가 해결되었다.
interface Rho {
    operator fun inc(): Sigma = TODO()
}

interface Sigma : Rho {
    fun sigma() = Unit
}

interface Tau {
    fun tau() = Unit
}

fun main(input: Rho) {
    var unknownObject: Rho = input

    if (unknownObject is Tau) {

        // inc() 연산자를 통해 Sigma로 스마트캐스트된다
        ++unknownObject

       // kotlin 2.0.0버전의 컴파일러는 unknownObject가 Sigma 타입이란 것을 알기 때문에 sigma() 호출이 가능하다
        unknownObject.sigma()

       // kotlin 2.0.0 이전 버전에서는 증감 연산자를 통해서 스마트캐스트를 해주지 않기 때문에 sigma()를 호출할 수 없다.

        // In Kotlin 2.0.0에서는 unknownObject가 Sigma로 스마트캐스트되어서 tau()를 호출할 수 없다
        // 하지만 2.0.0 이전 버전에서는 증감연산자를 통해 스마트캐스트가 되지 않아 Tau타입이 유지되어 호출이 가능하다
        unknownObject.tau()
    }
}

0개의 댓글