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 class
를Kotlin 1.3
버전에서 알파 테스트 버전으로,
Kotlin 1.4
버전에서 베타 테스트 버전으로 사용할 수 있었습니다.
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먼저 value
키워드를 통해서 value class
를 정의할 수 있습니다.
이렇게 정의된 value class
는 컴파일러의 도움을 받아 최적화의 대상이 됩니다.
@JvmInline
어노테이션은 코틀린의 다른 버전(Kotlin/JS, Kotlin/Native)과의
value class
호환을 위해 존재하는 어노테이션입니다.
정확한 정보는 다음 글에서 알아봐주시면 감사하겠습니다.
value class
의 진가는 바이트코드를 뜯어봤을 때 들어납니다.
그럼 위에서 언급했듯이 어떻게 최적화를 하는지 알아보도록 하겠습니다.
value class
는 JVM
이 바이트코드로 컴파일하는 과정에서
객체를 제거하고 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 class
인 duration
변수를
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 class
는equals()
,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
감사합니다, 배우고가요!