Development record of developer who study hard everyday.

, , , , , ,

코틀린 Inline class에 대하여 알아보자(번역)

 Kotlin Inline classes 공식문서 번역


오늘은 코틀린 인라인 클래스(Inline class)에 대해서 알아보자.

개발공부는 항상 공식문서를 기준으로 시작한다.

코틀린 공식홈페이지에서 문서를 번역한 것을 기본으로 한다.

                                              

때때로 변수 타입의 wrapper 클래스를 만들 때가 있다. 하지만 이것은 런타임에 과부하를 가져온다. 게다가, 원시형 타입으로 wrapper 클래스를 만들게되면, 성능은 더 좋지 않아진다.

이런 문제를 해결하기위해, 코틀린은 inline class라는 특별한 클래스를 제공한다. inline class는 value-based classes 중 하위항목이다. inline class는 id값을 가지지 않고 오직 값만 가질 수 있다.

1. 정의

inline class를 선언하기 위해서는, value라는 modifier를 사용한다.

JVM에 선언하기 위해서는 @JvmInline 어노테이션을 사용한다.

@JvmInline
value class Password(private val s: String)

✋ inline class에서 inline 한정자는 deprecated 되었다.


inline class는 주 생성자로 초기화된 하나의 프로퍼티만 가져야한다.

// No actual instantiation of class 'Password' happens
// At runtime 'securePassword' contains just 'String'
val securePassword = Password("Don't try this in production")


2. 멤버

Inline class는 일반 클래스의 기능을 제공한다.

예를 들어, 프로퍼티와 메소드를 선언할 수 있고 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
}

☝ Inline class의 프로퍼티는 backing field를 가지지 않는다. 그저 간단한 계산가능한 프로퍼티들을 가질 뿐이다.


3. 상속

Inline class는 interface로부터 상속받을 수 있다.

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는 class 상속이 불가능하다.

다시 말해, inline class는 항상 final이다.


4. 표현

generated code에서, 코틀린 컴파일러는 Inline class에 대한 wrapper 클래스를 유지한다.

Inline class는 런타임시 wrapper class나 원래의 자료형으로 표현된다. 이것은 Int가 원시형 타입 int나 wrapper class인 Integer로 표현되는 것과 같다.

Inline class는 다른 자료형으로 사용될 때마다 wrapper class로 감싸진다.

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

Inline class는 원래의 자료형과 wrapper class 둘 다 표현가능하기 때문에 

referential equality(===)가 의미없다.


Inline class는 generic 타입의 매개변수를 가질 수 있다. 이 경우 컴파일러는 generic을 Any?나 상위 타입의 매개변수로 매핑시킨다.


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

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


5. Mangling

Inline class는 원래의 타입으로 컴파일 되기 때문에 불명확한 에러를 발생시킨다.

예를들어, 아래와 같이 매개변수 충돌을 일으킬 수 있다.

@JvmInline
value class UInt(val x: Int)

// 'public final void compute(int x)'로 JVM상에서 컴파일됨
fun compute(x: Int) { }

// 마찬가지로 'public final void compute(int x)' JVM상에서 컴파일됨;;
fun compute(x: UInt) { }

이러한 현상을 막기위해, Inline class를 사용하는 함수들은 해시코드를 함수 이름에 더하는 방식으로 mangle된다.

따라서 fun compute(x : UInt)는 public final void compute-<hashcode>(int x) 로 표현된다.


자바에서도 Inline class를 사용할 수 있다.

이때, @JvmName을 추가하여 mangling을 지워야만한다.

@JvmInline
value class UInt(val x: Int)

fun compute(x: Int) { }

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


6. Inline class와 type alias

Inline class는 type alias와 매우 비슷해보인다. 실제로, 두 개념 모두 새로운 타입을 도입하고 런타임 시에 원래의 타입으로 표현된다.

하지만, 원래의 타입과 assignment-compatible이 결정적인 차이다.

Inline class는 원래의 타입과 assignment-compatible이 성립하지 않는다.

(assignment-compatible을 뭐라고 해석해야지?? 할당호환성?? ㅋㅋ)


다시말해, Inline class는 완전히 새로운 타입이다. 반면에, type alias는 원래 존재하는 타입의 다른 이름일 뿐이다.

typealias NameTypeAlias = String    //type alias 선언

@JvmInline
value class NameInlineClass(val s: String)    //Inline class 선언

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: String 타입 대신에 type alias 가능
acceptString(nameInlineClass) // not OK: String 타입 대신에 Inline class 불가능

// 반대로 해보자
acceptNameTypeAlias(string) // OK: type alias 대신에 String 타입 가능
acceptNameInlineClass(string) // Not OK: Inline class 대신에 String 타입 불가능
}


Share:
Location: 대한민국

댓글 없음:

댓글 쓰기