Kotlin 1.5에 추가된 value class에 대해 알아보자

Jaychy·2021년 6월 29일
16

알아가는 것

목록 보기
10/11
post-thumbnail

Github 코틀린으로 개발하면서 궁금했던 점에 대해 고찰하는 곳.

Kotlin 1.5

Kotlin 1.5 버전이 릴리즈 되면서 value class 추가,
sealed interface 알파 버전 추가 등의 여러 기능들이 추가되었습니다.

이번에는 그 기능들 중 inline class로 익숙할
value class에 대해서 알아보도록 하겠습니다.

문제 상황

우리가 기존에 코틀린으로 개발을 하면서 어떤 문제 상황을 겪었고,
value class가 등장하게 되었는지에 대해 알아보겠습니다.

fun reserveAlarm(alarmMessage: String, durationInMillis: Long) =
    println("$durationInMillis millis 후에 [$alarmMessage] 알람이 울립니다.")

이렇게 알람을 예약하고 예약된 메시지를 출력해주는 함수가 있습니다.
그럼 reserveAlarm() 메소드를 호출해봅시다.

reserveAlarm("학교 가야지", 2000L)

여기서 2000L이 2초라는 사실을 확실히 알 수 있는 사람은 없을 것입니다.
그래서 보통 래퍼 객체를 사용하게 되는데
이를 적용한 reserveAlarm() 함수는 다음과 같습니다.

fun reserveAlarm(alarmMessage: String, duration: Duration) =
    println("$duration.millis millis 후에 [$alarmMessage] 알람이 울립니다.")
    
reserveAlarm("학교 가야지", Duration.seconds(2))

이런식으로 Duration 클래스와 같은 래퍼 클래스를 사용하게 되면
함수의 가독성이 좋아진다는 장점이 있지만,
함수를 호출할 때마다 객체를 생성하여 호출해야한다는 비용이 발생하게 됩니다.

"이 비용을 절감할 수 있지 않을까?"해서 나온 클래스가
inline class 즉, value class 입니다.

이렇게 value class가 정식 릴리즈 되기 이전에
inline classKotlin 1.3 버전에서 알파 테스트 버전으로,
Kotlin 1.4 버전에서 베타 테스트 버전으로 사용할 수 있었습니다.

그래서 value class가 뭐냐

value class는 위에서 말했듯이 객체를 생성하는 비용을 줄여줍니다.
어떻게 바이트코드를 최적화하는지는 밑에서 자세하게 설명할 예정입니다.

위 예제를 참고하여 Duration 클래스를 선언해보겠습니다.

@JvmInline
value class Duration private constructor(
    val millis: Long,
) {
    companion object {
        fun millis(millis: Long) = Duration(millis)
        fun seconds(seconds: Long) = Duration(seconds * 1000)
    }
}

언뜻보기에는 일반 클래스와 비슷해보이지만 새로운 키워드가 두 개가 보입니다.

  • value keyword
  • @JvmInline annotation

1. value keyword

먼저 value 키워드를 통해서 value class를 정의할 수 있습니다.
이렇게 정의된 value class는 컴파일러의 도움을 받아 최적화의 대상이 됩니다.

2. @JvmInline annotation

@JvmInline 어노테이션은 코틀린의 다른 버전(Kotlin/JS, Kotlin/Native)과의
value class 호환을 위해 존재하는 어노테이션입니다.

정확한 정보는 다음 글에서 알아봐주시면 감사하겠습니다.

value class의 진가는 바이트코드를 뜯어봤을 때 들어납니다.
그럼 위에서 언급했듯이 어떻게 최적화를 하는지 알아보도록 하겠습니다.

어떻게 최적화를 하는데?

value classJVM이 바이트코드로 컴파일하는 과정에서
객체를 제거하고 value class의 프로퍼티로 대체합니다.

위에서 봤던 reserveAlarm() 메소드를 다시 한 번 보겠습니다.

fun reserveAlarm(alarmMessage: String, duration: Duration) =
    println("$duration.millis millis 후에 [$alarmMessage] 알람이 울립니다.")

이렇게 매개변수로 value class (Duration)가 있으면 바이트코드로 변환할 때
매개변수로 Duration 객체의 프로퍼티를 가진 함수로 변환합니다.
즉, 다음과 같이 변환됩니다.

fun reserveAlarm-UiEZ_Y8(alarmMessage: String, duration: Long) =
    println("$duration millis 후에 [$alarmMessage] 알람이 울립니다.")

이렇게 매개변수 중 value classduration 변수를
Duration 객체 타입에서 Long 타입으로 변경한 함수로 변환합니다.
그리고 함수 이름 뒤에 -[a-zA-Z_]{7}을 붙여
맹글링(mangling)하는 작업을 거칩니다.

맹글링하는 이유가 뭔가요?

@JvmInline
value class(val s: String)
fun test(a: A) {}
fun test(s: String) {}

이렇게 두 함수가 같이 존재하게 되면
바이트코드로 컴파일될 때 두 테스트 함수는 오버로딩이 아니라 중복이 됩니다.
그래서 맹글링을 통해 함수가 겹치는 일이 없도록 변경하는 것입니다.

이렇게 매개변수로 value class를 받는 것 이외에도
지역변수로 사용하든, 반환 값으로 사용하든
모두 컴파일러가 알아서 최적화를 진행해줍니다.

데이터 클래스와의 차이점 짚어주세요!

일단 사용하는 부분에서 차이가 드러나지만
데이터를 저장하는 클래스라는 점과 값을 저장하는 클래스라는 점에서
같은 의미로 느껴지기 때문에 차이점을 확실히 알고가는 것이 좋을 것 같습니다.

1. 자동 생성하는 메소드가 다릅니다.

데이터 클래스는 equals(), toString(), hashCode(), copy(),
componentN() 메소드를 자동 생성하는 반면에
value classequals(), toString(), hashCode() 메소드만
자동 생성합니다.

2. === 연산을 컴파일 단계에서 금지합니다.

또한 데이터 클래스는 == 연산 (자바의 equals), === 연산 (자바의 ==)
모두 지원하지만, value class== 연산만 지원하고
=== 연산은 지원하지 않습니다.

value class Box(element: String)

val chineseBanana = Box("chinese banana")
val koreanBanana = Box("chinese banana")
println(chineseBanana == koreanBanana)
println(chineseBanana === koreanBanana)
// Identity equality for arguments of types Box and Box is forbidden

3. 반드시 val 프로퍼티만 허용합니다.

데이터 클래스의 프로퍼티는 Mutable하든, Immutable하든 관계 없습니다.
하지만 value class는 반드시 Immutable한 프로퍼티만 정의 가능합니다.

value class Box(var mutableProperty)
// Value class primary constructor must have only final read-only (val) property parameter

4. 하나의 프로퍼티만 가능합니다.

현재는 value class에 프로퍼티를 하나만 정의 가능합니다.
이는 추후에 개선될 것이라고 공식 입장을 밝혔으니
추후 업데이트를 기다려야할 것 같습니다.

value class Box(
    val element1: String,
    val element2: String,
) // Inline class must have exactly one primary constructor parameter

참조 문서

kotlin value class - Mahendran
inline class - Kotlin
Data classes vs new value classes

profile
아름다운 코드를 꿈꾸는 백엔드 주니어 개발자입니다.

4개의 댓글

comment-user-thumbnail
2022년 1월 27일

감사합니다, 배우고가요!

1개의 답글

"맹글링하는 이유가 뭔가요?" 에서 value class 의 이름이 빠져 있습니다.

@JvmInline
value class(val s: String) // value class A(val s: String)
fun test(a: A) {}
fun test(s: String) {}

답글 달기
comment-user-thumbnail
2024년 7월 21일

잘 읽고 갑니다. Type Driven Development에서 여기까지 왔네요. 포스팅하면서 신나하는게 느껴지는 것 같아요

답글 달기