객체 지향 생활 원칙엔 모든 원시 값과 문자열을 포장한다 라는 문장이 있죠
원시값을 클래스로 감싸는 것은 항상 효과적일까요?
정답부터 얘기하자면, ❌ 항상 효과적이진 않습니다 ❌
컴파일 때 크기가 결정되는 원시값의 경우, 스택(stack)에 메모리가 할당됩니다
이는 해당 스코프를 벗어나면 메모리가 자동 해제되죠!
반면, 런타임에 크기가 결정되는 객체의 경우 힙(heap)에 메모리가 할당됩니다
힙 메모리는 스택보다 속도가 느리고, 관리 비용이 높아 런타임 오버헤드를 발생시키는 원인이 될 수 있습니다
또한 원시 타입은 일반적으로 런타임에서 강력하게 최적화되지만, 그들을 감싸는 wrapper class는 특별한 최적화를 받지 않기 때문에 성능 저하가 발생할 수 있습니다
이를 해결하기 위해 Kotlin이 도입한 개념이 value class입니다 🔥
💡 과거엔 inline class라는 개념이 있었지만, koline 1.5 이후엔 value class로 대체됐다고 합니다!
코드를 통해 value class의 동작 과정을 봅시다
@JvmInline
value class LottoNumber(val number: Int)
value 키워드를 class 앞에 붙이고 선언하면 간단하게 value class를 사용할 수 있습니다
💡 @JvmInline annotation
"이 클래스를 Inline해 줘!" 라고 컴파일러에게 알리는 역할로,
코틀린의 다른 버전과 value class 호환을 위해 항상 적어 주어야 합니다!
value class로 선언된 객체는 바이트 코드로 컴파일하는 과정에서 객체를 제거하고 해당 클래스의 프로퍼티로 대체되는데요,
// 1. 이렇게 함수를 정의하면
fun printLottoNumber(lottoNumber: LottoNumber) {
println("${lottoNumber.number} 입니다!")
}
⬇️
// 2. 컴파일 과정에서 파라미터가 원시값으로 저절로 최적화됨
fun printLottoNumber(lottoNumber: Int) {
println("$lottoNumber 입니다!")
}
위처럼 LottoNumber 객체를 함수 파라미터로 받으면, 바이트 코드로 변환할 때 LottoNumber의 프로퍼티인 number: Int가 함수의 파라미터로 대체됩니다

글로만 보면 어려우니, LottoNumber는 value class로, LottoNumber2는 그냥 class로 만들어서 java 코드로 디컴파일 해 봅시다 🔥

디컴파일된 printLottoNumber 함수의 파라미터에 주목해 볼까요
printLottoNumber 함수의 파라미터가 LottoNumber -> Int 로 바뀌었고,
printLottoNumber2 함수의 파라미터는 LottoNumber2 객체를 그대로 받고 있네요
이렇게 value class를 이용하면 컴파일러가 알아서 최적화를 진행해 주므로, 객체를 생성하면서 생기는 오버헤드를 방지할 수 있습니다!
equals(), toString(), hashcode() 함수를 자동 생성한다
=== 연산을 지원하지 않는다
➡️ value class는 객체가 아닌, 값 자체로 변환되므로 참조값을 비교할 수 없다
💡
===과==의 차이점을 모른다면, 동등성과 동일성의 차이에 대해 공부하자!
단 하나의 val 프로퍼티만 가질 수 있다
backing, lateinit field 선언이 불가하다
추가로 Inline 개념을 알면 좋을 것 같아 뇌피셜로 끄적여 보겠습니다...
fun add(x: Int, y: Int) = x + y
fun main() {
val total = add(10, 20) // total = 30
}
위처럼 인자로 받은 두 정수의 합을 계산해 주는 add 함수가 있다고 가정해 봅시다
main 함수에서 add 함수를 호출했을 때 stack은 어떤 상태일까요?

이렇게 main 함수 위에 add 함수가 쌓여져 있는 형태일 겁니다
하지만 add가 Inline Function 이라면?
fun main() {
val x = 10
val y = 20
val total = x + y
}
내부적으로 위와 같이 컴파일 되고,
add 호출부 대신 add의 정의부가 main 안에 들어가게 됩니다

stack 관점으로 보면 이렇게 되죠
main 함수가 차지하는 메모리는 예전보다 많아졌지만,
add 함수를 호출하면서 생기는 오버헤드는 줄어드는 것을 알 수 있습니다
마지막 그림 스택 색이 조금 넘치게 칠해졌는데, 혹시 스택 오버플로우를 암시하신 걸까요 ?!? 🧐🧐