[Kotlin] Inline class

코랑·2023년 4월 25일
0

android

목록 보기
6/16

정의

비지니스 로직에서 타입을 감싸는 표현이 필요할 때 사용

// use value modifier
value class Password(private val s: String)
// For JVM backends
@JvmInline
value class Password(private val s: String)

Member

일반 클래스의 일부 기능 제공 => properties와 functions, init

@JvmInline
value class Name(val s: String) {
    init {
        require(s.length > 0) { }
    }

    val length: Int
        get() = s.length

    fun greet() {
        println("Hello, $s")
    }
}

fun main() {
    val name = Name("Kotlin")
    name.greet() // method `greet` is called as a static method
    println(name.length) // property getter is called as a static method
}

backing field 제공 안함.
간단한 계산 properties만 제공.(lateinit/delegated properties가 아니라)

상속

인터페이스 상속 가넝. 클래스 계층구조에 낄수없음.
무슨말이냐? inline class는 항상 final이라 확장이 안됨

interface Printable {
    fun prettyPrint(): String
}

@JvmInline
value class Name(val s: String) : Printable {
    override fun prettyPrint(): String = "Let's $s!"
}

fun main() {
    val name = Name("Kotlin")
    println(name.prettyPrint()) // Still called as a static method
}

표현

코드를 만들때 코틀린 컴파일러는 각 인라인 클래스의 래퍼를 계속 가지고있음.
inline class 는 런타임에 원시 타입과 래퍼 클래스로 표현될 수 있음.(Int가 int와 Integer 로 표현되는거처럼)
당연하게 컴파일러는 성능이나 코드 최적화를 위해 원시타입을 사용하는걸 더 선호한다 근데 래퍼를 유지해야할 필요가 있기 때문에, 일반적으로 인라인 클래스는 다른 타입으로 사용될때마다 박싱된다.

interface I

@JvmInline
value class Foo(val i: Int) : I

fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}

fun <T> id(x: T): T = x

fun main() {
    val f = Foo(42)

    asInline(f)    // unboxed: used as Foo itself
    asGeneric(f)   // boxed: used as generic type T
    asInterface(f) // boxed: used as type I
    asNullable(f)  // boxed: used as Foo?, which is different from Foo

    // below, 'f' first is boxed (while being passed to 'id') and then unboxed (when returned from 'id')
    // In the end, 'c' contains unboxed representation (just '42'), as 'f'
    val c = id(f)
}

그래서 referential equality(===)를 사용할 수 없음

인라인 클래스는 제네릭 타입 파라미터를 사용할 수 있음=> 컴파일러는 제네릭 타입을 Any?이나 타입 상한으로 매핑함.
근데 이거 실험적 피처라 언제 없어질지 모르고 컴파일러 옵션 -language-version 1.8이 필요함

@JvmInline
value class UserId<T>(val value: T)

fun compute(s: UserId<String>) {} // compiler generates fun compute-<hashcode>(s: Any?)

Mangling

인라인 클래스가 원시타입으로 컴파일 되기 때문에 다양한 모호한 에러를 일으킬 수 있다.

@JvmInline
value class UInt(val x: Int)

// Represented as 'public final void compute(int x)' on the JVM
fun compute(x: Int) { }

// Also represented as 'public final void compute(int x)' on the JVM!
fun compute(x: UInt) { }

위 문제를 해결하기 위해 펑션 이름에 stable hashcode를 추가해서 뒤섞음.
그래서 fun compute(x: UInt)public final void compute-<hashcode>(int x)로 표현된다
그럼 자바에서는? mangling이 안돼서@JvmName을 붙여줌

@JvmInline
value class UInt(val x: Int)

fun compute(x: Int) { }

@JvmName("computeUInt")
fun compute(x: UInt) { }

InlineClass vs Type aliases

인라인 클래스는 type aliase랑 갱장히 비슷해 보이는데,
실제로도 둘 다 새로운 타입을 소개하는것으로 보이고 둘다 런타임에 원시타입으로 표현된다.
그러ㅓ나 가장 큰 다른점은 type-aliases는 기본 타입에 할당이 가능하다(그리고 원시 타입이 같은 다른 type alias에도 가능) 근데 인라인은 안됨.
그러니까 인라인은 진짜 새로운 타입인거고, type-aliase는 원시 타입에다가 이름을 입혀주는거라 생각하면된다!

typealias NameTypeAlias = String

@JvmInline
value class NameInlineClass(val s: String)

fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}

fun main() {
    val nameAlias: NameTypeAlias = ""
    val nameInlineClass: NameInlineClass = NameInlineClass("")
    val string: String = ""

    acceptString(nameAlias) // OK: pass alias instead of underlying type
    acceptString(nameInlineClass) // Not OK: can't pass inline class instead of underlying type

    // And vice versa:
    acceptNameTypeAlias(string) // OK: pass underlying type instead of alias
    acceptNameInlineClass(string) // Not OK: can't pass underlying type instead of inline class
}

InlineClass and delegation

인라인 클래스에서 델리게이션 사용하기~

interface MyInterface {
    fun bar()
    fun foo() = "foo"
}

@JvmInline
value class MyInterfaceWrapper(val myInterface: MyInterface) : MyInterface by myInterface

fun main() {
    val my = MyInterfaceWrapper(object : MyInterface {
        override fun bar() {
            // body
        }
    })
    println(my.foo()) // prints "foo"
}

0개의 댓글