728x90
서론
원시값들을 모두 포장하다보니 클래스 외부에서 비교를 할 때 아래와 같이 코드가 길어지고 복잡해지는 경우가 많았다.
- 예제) 로또의 번호들이 중복되었는지 검사하는 코드
require(numbers.size == numbers.distinctBy { it.number }.size) {
LOTTERY_NUMBERS_DUPLICATE_ERROR
}
따라서, 외부에서 사용시 복잡해지지 않도록 equals를 override하였다.
두 객체를 비교할 때 객체의 주소가 아닌 객체의 내부 변수를 이용해 비교를 하도록 하였다.
하지만 이렇게 원시값을 포장할 때는 class 보다는 data class가 더 적절할 수 있다.
때에 따라서는 value class가 더 적절할 수도 있다.
class, data class, value class의 특징을 정리해 보았다.
각각의 클래스를 사용해 같은 코드가 어떻게 다른 것인지 살펴보도록 하자.
class (override)
class LotteryNumber(
val number: Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LotteryNumber
if (number != other.number) return false
return true
}
override fun hashCode(): Int = number
override fun toString(): String = "$number"
}
data class
class와 data class는 equals(), hashCode(), toString(), componentN(), copy()의 로직이 다르다.
코틀린 코드를 자바 코드로 역컴파일 하여 확인할 수 있다.
- equals()
- class에서는 객체의 주소를 사용해 동일성을 비교한다.
- data class에서는 객체 내부 변수를 사용해 동등성을 비교한다.
// class
public boolean equals(@Nullable Object other) {
if ((LotteryNumber)this == other) {
return true;
} else if (Intrinsics.areEqual(this.getClass(), other != null ? other.getClass() : null) ^ true) {
return false;
} else if (other == null) {
throw new NullPointerException("null cannot be cast to non-null type lotto.domain.LotteryNumber");
} else {
LotteryNumber var10000 = (LotteryNumber)other;
return this.number == ((LotteryNumber)other).number;
}
}
public int hashCode() { return this.number; }
// data class
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof LotteryNumber) {
LotteryNumber var2 = (LotteryNumber)var1;
if (this.number == var2.number) {
return true;
}
}
return false;
} else {
return true;
}
}
public int hashCode() { return Integer.hashCode(this.number); }
- toString()
- class: 객체의 주소
- data class: LotteryNumber(number=42)
- data class는 class를 위와 같은 형태의 String으로 바꿔준다.
따라서 동등성 비교를 할 때는 문제가 되지 않지만
객체 내부의 변수를 그대로 출력하고자 한다면 오버라이드를 해주어야 한다.
- copy()
- 동일한 객체로 복사를 한다.
value class (= inline class)
- data class와 equals(), hashCode(), toString()의 로직이 같다.
- 객체를 생성할 때 발생하는 비용을 줄여주는 class
- 하나의 immutable 매개변수만 가질 수 있고, @JvmInline annotaion과 함께 사용해야 한다.
- 그 외에도 data class와 다르게 equals, toString, hasCode만 자동 생성이 되고,
"=="만 허용이 된다. ("===" 불가)
(동등성을 만족할 때 동일성도 만족하기 때문)
@JvmInline
value class LotteryNumber(
private val number: Int
) {
...
}
- Mangling (value class가 객체 생성 비용을 줄여주는 방식)
- 컴파일 중에는 LotteryNumber 타입이지만 바이트코드에서 Int로 변경하는 방식
(일반 class를 생성하는 것보다 기본형을 생성하는 것이 비용 발생이 적다.)
- 컴파일 중에는 LotteryNumber 타입이지만 바이트코드에서 Int로 변경하는 방식
결론
- data class가 적합할 때
- 내부 변수가 가변값이거나 여럿일 때
- 동일성 비교를 하지 않고, 동등성 비교만 필요할 때
(같은 내부 변수를 가지는 객체는 여럿 존재할 수 있음)
- value class가 적합할 때
- 내부 변수가 불변값 하나일 때
- 동일성 비교를 하지 않고, 동등성 비교만 필요할 때
(같은 내부 변수가 곧 같은 객체, 원시 타입이기 때문)
이렇게 간단히 정리해볼 수 있다.
하지만, value class를 사용할 때는 data class에 비해 주의할 점이 많다.
(부생성자를 통해 원시값을 받아 value class 타입으로 포장할 수 없는 점 등)
참고
728x90
'kotlin > insight' 카테고리의 다른 글
Kotlin에서의 프로퍼티, Java에서의 필드와 프로퍼티 (0) | 2023.04.10 |
---|---|
intellij에서 ktlint를 사용해 Kotlin 컨벤션 준수하기 (2) | 2023.02.07 |
댓글