
코틀린 data class는 DTO를 짧게 쓰는 문법처럼 보이지만, 실제로는 값 중심 객체라는 의도를 코드에 드러내는 방법입니다. `equals`, `hashCode`, `toString`, `copy` 같은 반복 메서드를 자동으로 만들어 줍니다.
핵심은 객체의 정체성이 값에 있는지, 행동과 규칙에 있는지 먼저 나누는 것입니다. 이 글은 Kotlin 공식 data class 문서를 기준으로 언제 data class가 자연스럽고 언제 일반 class가 나은지 정리합니다.

코틀린 data class를 먼저 한 줄로 정리하면
data class는 값 중심 객체를 만들 때 반복되는 메서드를 자동으로 만들어 주는 Kotlin 문법입니다. 주 생성자에 선언한 프로퍼티를 기준으로 equals, hashCode, toString, componentN, copy가 생성됩니다.
data class User(
val id: Long,
val name: String
)
val a = User(1, "kim")
val b = User(1, "kim")
println(a == b) // true일반 class였다면 기본적으로 참조 비교가 될 수 있지만, data class는 주 생성자 프로퍼티 값을 기준으로 비교합니다.
자동 생성되는 메서드는 무엇일까
- `equals`와 `hashCode`: 값 기준 비교와 hash 컬렉션 사용에 필요하다
- `toString`: 디버깅하기 쉬운 문자열 표현을 제공한다
- `componentN`: 구조 분해 선언에 사용된다
- `copy`: 일부 값만 바꾼 새 객체를 만들 때 사용된다
val user = User(1, "kim")
val renamed = user.copy(name = "lee")
val (id, name) = renamed
println("$id / $name")특히 `copy`는 UI 상태나 요청 모델처럼 기존 값을 유지하면서 일부만 바꿔야 할 때 읽기 좋은 코드를 만듭니다.
DTO와 UI 상태에는 왜 잘 맞을까
서버 응답, 화면 상태, 설정값처럼 객체의 의미가 필드 값 묶음에 가까운 경우 data class가 잘 맞습니다. 데이터가 무엇을 담는지 생성자에서 바로 보이고, 비교와 출력도 자연스럽습니다.
data class LoginUiState(
val email: String = "",
val password: String = "",
val loading: Boolean = false,
val errorMessage: String? = null
)이런 상태 객체는 Compose나 ViewModel에서 일부 값만 바꿔 새 상태로 내보내는 방식과도 잘 맞습니다.
mutable property를 넣을 때 조심할 점
data class 안에 `var`나 mutable list를 넣으면 값 객체처럼 보이지만 내부 상태는 바뀔 수 있습니다. 그러면 copy를 했는데 내부 컬렉션을 공유하는 등의 문제가 생길 수 있습니다.
data class Team(
val name: String,
val members: MutableList<String>
)
val a = Team("dev", mutableListOf("kim"))
val b = a.copy()
b.members.add("lee")
println(a.members) // [kim, lee]data class가 자동으로 깊은 복사를 해 주는 것은 아닙니다. 컬렉션을 다룰 때는 불변 리스트를 쓰거나 복사 범위를 명확히 해야 합니다.
일반 class가 더 자연스러운 경우
객체가 단순 데이터 묶음이 아니라 규칙과 행동을 중심으로 움직인다면 일반 class가 더 자연스러울 수 있습니다. 예를 들어 생성 조건, 상태 변경 규칙, 외부 자원 관리가 중요한 객체라면 data class로 모든 것을 열어두지 않는 편이 낫습니다.
class BankAccount private constructor(
private var balance: Long
) {
fun deposit(amount: Long) {
require(amount > 0)
balance += amount
}
}이런 객체는 값 비교보다 유효한 상태 변경이 더 중요합니다. 자동 copy가 오히려 잘못된 상태 생성을 쉽게 만들 수 있습니다.
Java record, Python dataclass와 비교하면
Kotlin data class는 Java record나 Python dataclass와 비슷하게 데이터 중심 객체를 짧게 표현합니다. 다만 각 언어의 불변성 기본값, 생성자 규칙, 프레임워크 호환성은 다르므로 이름만 보고 같은 도구처럼 쓰면 안 됩니다.
실무 체크리스트
- 객체의 정체성이 필드 값 묶음인지 확인한다
- 주 생성자에 비교 기준이 되는 프로퍼티를 둔다
- mutable collection을 넣을 때 copy가 얕은 복사임을 확인한다
- 도메인 규칙과 행동이 중심이면 일반 class를 검토한다
- UI state와 DTO에서는 data class를 우선 후보로 둔다
정리
코틀린 data class를 이해할 때는 문법 이름보다 문제를 일으키는 지점을 먼저 봐야 합니다. 오늘 글의 핵심은 겉으로 비슷해 보이는 코드가 어떤 기준으로 다르게 동작하는지 분리해 보는 것입니다.
함께 보면 좋은 내부 글은 코틀린 scope function let run apply also with 차이, 코틀린 typealias는 언제 쓸까, 코틀린 when은 switch와 무엇이 다를까입니다. 외부 기준은 Kotlin Docs – Data classes, Kotlin Docs – Classes, Kotlin Docs – Properties를 확인했습니다.