[kotlin] inline value class

할렐루카·2024년 5월 21일
0

kotlin진심펀치

목록 보기
4/6

value class

value를 class로 감싸서 특정 도메인에 특화하여 유용하게 사용할 수 있는 경우가 있다. 하지만 class는 heap에 객체가 생성(boxing)되기 때문에 overhead가 발생한다.
kotlin은 이런 overhead를 해결해주는 inline value class를 제공한다.
value class는 단일 property만 가지며 그저 값을 들고만 있는다.


위와 같이 같은 String 이지만 다른 format을 적용하고 싶을 때 value class를 사용할 수 있을 것 같다.

inheritance


공식 문서에 따르면 상속관계에 포함시킬 수는 없다. Java Code로 Decompile해서 확인하면 final로 선언되어 있는 것을 확인할 수 있다.

representation

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)
}

온전히 value class로써 사용될 때만 boxing을 안하는 것으로 보인다.
generic Type이나 구현체 혹은 nullable 타입으로 표현될 때는 boxing이 된다고 한다.

Java Code로 확인하면 synthetic method로 boxing과 unboxing 코드가 생성되어 있다.

  • synthetic은 jvm이 생성해주는 코드를 의미한다.

mangling

inline class들은 기본형으로 compile 되기 때문에 function 등에서 사용될 때 signautre가 중복되는 문제가 생길 수 있다.

@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) { }

위의 Customer, Manager 클래스의 formatName 함수의 경우에도 String.formatName(): String 으로 signature가 같기 때문에 같은 상황이라고 볼 수 있는데 Java Code를 살펴보면


value class인 Customer의 extension fun 이름에는 hash 값이 붙어있는 것을 확인할 수 있다. 이 hashcode를 통해 signature 중복을 피할 수 있다.

inline classes vs type aliases

사실 가장 궁금했던 부분이다. 기존의 프로젝트에서도 value class보다는 type aliases를 주로 사용했었기 때문인데

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
}

inline class compile 시점에 타입 점검을 하고 그 후에 기본형으로 변환되기 때문에 기본형으로 자동으로 형변환되지 않는다.

참고

https://kotlinlang.org/docs/inline-classes.html#inline-classes-and-delegation

0개의 댓글