본문 바로가기
개발&프로그래밍

[kotlin] 인라인 값 클래스(Inline Value Classes)

by 재아군 2024. 8. 15.

Kotlin의 인라인 값 클래스(Inline Value Classes)

Kotlin의 인라인 값 클래스(Inline Value Classes)는 성능 최적화와 타입 안전성을 동시에 제공하는 강력한 기능입니다.

이 문서에서 인라인 값 클래스의 다양한 사용법을 살펴봅니다.

 

 

인라인 값 클래스 기본 (Inline Value Class Basics)

인라인 값 클래스는 단일 값을 래핑하여 보다 도메인 특화된 타입을 만들 때 유용합니다. 이는 값 기반 클래스의 하위 집합으로, 아이덴티티가 없고 오직 값만을 보유합니다.

value class Password(private val s: String)

// JVM 백엔드를 위한 선언
@JvmInline
value class Password(private val s: String)

인라인 클래스는 단일 프로퍼티를 가져야 하며, 이 프로퍼티는 주 생성자에서 초기화되어야 합니다:

val securePassword = Password("Don't try this in production")

런타임에서 securePassword는 실제로 String 값만을 포함합니다.

 

멤버 (Members)

인라인 클래스는 일반 클래스의 일부 기능을 지원합니다:

@JvmInline
value class Person(private val fullName: String) {
    init {
        require(fullName.isNotEmpty()) {
            "Full name shouldn't be empty"
        }
    }

    constructor(firstName: String, lastName: String) : this("$firstName $lastName") {
        require(lastName.isNotBlank()) {
            "Last name shouldn't be empty"
        }
    }

    val length: Int
        get() = fullName.length

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

fun main() {
    val name1 = Person("Kotlin", "Mascot")
    val name2 = Person("Kodee")
    name1.greet() // 정적 메서드로 호출됨
    println(name2.length) // 프로퍼티 게터가 정적 메서드로 호출됨
}

 

상속 (Inheritance)

인라인 클래스는 인터페이스를 구현할 수 있지만, 다른 클래스를 상속할 수는 없습니다:

interface Printable {
    fun prettyPrint(): String
}

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

 

표현 (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 main() {
    val f = Foo(42)

    asInline(f)    // 언박싱: Foo 자체로 사용됨
    asGeneric(f)   // 박싱: 제네릭 타입 T로 사용됨
    asInterface(f) // 박싱: 타입 I로 사용됨
    asNullable(f)  // 박싱: Foo?로 사용됨 (Foo와 다름)
}

 

맹글링 (Mangling)

인라인 클래스를 사용하는 함수는 이름에 안정적인 해시코드가 추가되어 맹글링됩니다. 이는 플랫폼 시그니처 충돌을 방지합니다.

 

Java 코드에서 호출 (Calling from Java code)

Java 코드에서 인라인 클래스를 사용하는 함수를 호출하려면 @JvmName 어노테이션을 사용하여 맹글링을 수동으로 비활성화해야 합니다:

@JvmInline
value class UInt(val x: Int)

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

 

인라인 클래스 vs 타입 별칭 (Inline classes vs 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
    acceptString(nameInlineClass) // 오류

    acceptNameTypeAlias(string) // OK
    acceptNameInlineClass(string) // 오류
}

인라인 클래스는 새로운 타입을 도입하는 반면, 타입 별칭은 기존 타입의 대체 이름일 뿐입니다.

 

인라인 클래스와 위임 (Inline classes 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() {
            // 구현
        }
    })
    println(my.foo()) // "foo" 출력
}

 

Kotlin의 인라인 값 클래스는 성능 최적화와 타입 안전성을 동시에 제공하는 강력한 기능입니다. 특히 원시 타입을 래핑하거나 도메인 특화 타입을 만들 때 유용합니다. 하지만 인라인 클래스의 사용은 신중하게 고려해야 합니다:

  1. 성능 이점: 불필요한 객체 생성을 줄여 성능을 향상시킬 수 있습니다.
  2. 타입 안전성: 기본 타입과 구분되는 새로운 타입을 생성하여 타입 안전성을 높입니다.
  3. API 설계: 도메인 특화 타입을 만들어 API를 더 명확하게 설계할 수 있습니다.
  4. 호환성: Java 코드와의 호환성을 고려해야 합니다.
  5. 제한사항: 인라인 클래스는 단일 프로퍼티만을 가질 수 있고, 다른 클래스를 상속할 수 없습니다.

인라인 값 클래스를 적절히 활용하면 코드의 표현력과 성능을 동시에 개선할 수 있습니다. 하지만 과도한 사용은 코드의 복잡성을 증가시킬 수 있으므로, 프로젝트의 요구사항과 특성을 고려하여 적절히 사용하는 것이 중요합니다.

 

 

 


Kotlin, 인라인클래스, 값클래스, 성능최적화, 타입안전성, JVM, 맹글링, 박싱, 언박싱, API설계, 코틀린문법, 안드로이드개발, 서버개발, 메모리관리, 코드최적화

댓글