값을 담는 것이 목적인 클래스의 보일러플레이트를 없앤다.
data class User(val id: Long, val name: String, val age: Int)
// 자동 생성되는 것
// equals / hashCode / toString / copy / componentN
copy — 불변 객체를 다루는 방법
val u = User(1, "철수", 20)
val older = u.copy(age = 21) // 나머지는 그대로, age 만 바꾼 새 객체
상태 업데이트의 표준 관용구다. StateFlow로 화면 상태를 다룰 때 계속 쓴다.
_uiState.update { it.copy(isLoading = false, items = result) }
구조 분해
val (id, name, age) = u // componentN 덕분에 가능
for ((key, value) in map) { ... }
순서 기반이라는 게 함정이다 — 프로퍼티 순서를 바꾸면 구조 분해하는 쪽이 조용히 잘못된 값을 받는다.
주 생성자에 없는 프로퍼티는 제외된다
data class User(val id: Long) {
var nickname: String = "" // equals · hashCode · toString 에 포함되지 않는다
}
User(1).apply { nickname = "A" } == User(1).apply { nickname = "B" } // true!
가장 자주 데는 지점이다. 동등성에 넣고 싶으면 주 생성자로 올려야 한다.
제약
- 주 생성자에 파라미터가 최소 하나 있어야 한다
- 모두 val 또는 var 여야 한다
- abstract · open · sealed · inner 가 될 수 없다
value class — 래퍼 비용 없이 타입을 나눈다
@JvmInline
value class UserId(val value: Long)
@JvmInline
value class OrderId(val value: Long)
fun find(id: UserId) { ... }
find(OrderId(1)) // 컴파일 에러 — 둘 다 Long 이지만 섞이지 않는다
컴파일되면 대부분 Long 그대로로 인라인된다 → 래퍼 객체가 안 생긴다
단, 다음 경우에는 실제 객체가 만들어진다
- nullable 로 쓸 때 (UserId?)
- 제네릭 타입 인자로 쓸 때 (List<UserId>)
- 인터페이스 타입으로 받을 때
"Long 두 개를 헷갈려 뒤바꿔 넘기는" 버그를 컴파일 시점에 막으면서 성능 비용이 거의 없다.
면접 함정
- ❌ "data class는 불변이다" →
var프로퍼티를 쓸 수 있고, 담긴 컬렉션이 가변이면 얕은 불변이다. - ❌ "copy는 깊은 복사" → 얕은 복사다. 내부 객체는 공유된다.
언제 쓰지 말아야 하나
- 동작이 주된 책임인 클래스 (서비스 · 매니저)
- 상속 계층이 필요할 때 (data class 는 open 이 될 수 없다)
- 식별자로만 동등성을 판단해야 하는 엔티티
- data class 는 모든 프로퍼티를 비교하므로 의미가 어긋난다
// 엔티티라면 equals 를 직접 정의하는 편이 맞다
class User(val id: Long, var name: String) {
override fun equals(other: Any?) = other is User && id == other.id
override fun hashCode() = id.hashCode()
}
안드로이드에서의 관용구
// 화면 상태 전체를 하나의 data class 로
data class HomeUiState(
val isLoading: Boolean = false,
val items: List<Item> = emptyList(),
val error: String? = null
)
// update + copy 로 부분 갱신
_uiState.update { it.copy(isLoading = true) }
// Parcelable 도 한 줄로
@Parcelize
data class User(val id: Long, val name: String) : Parcelable
기본값을 주면 copy로 필요한 것만 바꿀 수 있어 상태 관리가 크게 단순해진다.