Kotlin Data Class 정복하기

rivermoon·2025년 3월 22일
post-thumbnail

🤔 왜 Data Class가 필요할까?

Kotlin을 쓰다 보면 데이터를 담기 위한 클래스를 자주 만들게 됩니다.
사용자 정보, 서버 응답, UI 상태 등 단순한 값 객체를 표현할 일이 많죠.

그런데 일반 클래스에서는 아래와 같은 불편함이 있습니다..

  • equals() 오버라이딩으로 값 비교 구현
  • hashCode() 구현 안 하면 Set, Map에서 오작동
  • toString() 구현 없으면 디버깅 시 불편
  • 복사를 위해 copy() 같은 함수 직접 작성해야 함

이 모든 반복 작업을 자동으로 해결해주는 기능이 바로 Kotlin의 Data Class입니다.
단순한 구조의 데이터를 다루는 데 최적화된 기능이죠.


🔍 Data Class란?

Kotlin에서 data 키워드를 사용한 클래스는 컴파일러가 다음과 같은 함수들을 자동 생성해주는 데이터 전용 클래스입니다.

✅ 자동 생성 함수

  • equals(other: Any?) → 값 비교
  • hashCode() → Set, Map key로 사용 가능
  • toString() → 객체의 필드를 문자열로 출력
  • copy() → 필드 일부를 변경한 새 객체 생성
  • componentN() → 구조 분해 할당 지원
data class User(val name: String, val age: Int)

이렇게 선언만 해주면 저 5가지 기능이 자동으로 제공됩니다.
그래서 부수적인 오버라이딩이 없어도 값 비교, 출력, 복제가 가능하죠.

🧪 기본 사용법

val user1 = User("Alice", 25)
val user2 = user1.copy(age = 30)

println(user1) // User(name=Alice, age=25)
println(user2) // User(name=Alice, age=30)

println(user1 == user2) // false (값 비교)
  • copy()를 통해 객체를 손쉽게 복제하고 일부 값만 변경 가능
  • == 연산자는 equals() 기반으로 작동 → 구조 기반 값 비교
  • 디버깅 시 toString()으로 사람이 보기 쉽게 출력

⚠️ Data Class의 규칙과 제약사항

몇 가지 제약 조건이 존재하는데 기억하셔야됩니다.

❗ 필수 조건

1. Primary constructor에 최소 하나의 val 또는 var가 있어야 함

data class Empty // ❌ Error: No parameters
data class Person(val name: String) // ✅ OK

2. 다음과 같은 키워드를 함께 사용할 수 없음

  • abstract
  • open
  • sealed
  • inner

3. 상속 불가능 함
왜? Data Class는 final임.

data class Child() : Parent() // ❌ 불가능

🔄 구조 분해

Data Class는 componentN() 함수를 자동으로 생성하므로 구조 분해 할당이 가능합니다.

val (name, age) = user1
println("이름: $name, 나이: $age")
  • 내부적으로 component1()name, component2()age 함수가 호출됨.
  • 리스트, Pair, Map Entry 등에서도 구조 분해 가능 (확장 개념)

🤯 중요한 키워드 equals(), copy()

✅ 상태 비교 예시 (ViewModel)

if (oldState != newState) {
    _uiState.value = newState
}
  • equals() 자동 생성 덕분에, 값이 동일하면 UI 갱신 생략 가능
  • copy()로 불변성 유지하면서 상태 변경 가능

✅ 예: Repository 캐시 처리

val cachedUser = repo.getUser()
if (cachedUser != networkUser) {
    repo.saveUser(networkUser)
}
  • equals()가 없으면 참조 주소 비교만 하게 되어 예상과 다르게 작동할 수 있음.

🧱 Kotlin 1.9+의 Record-like 기능

Java 16부터 등장한 record는 Kotlin의 Data Class와 유사한 목적을 가집니다.
Kotlin은 이에 대응해 다양한 실험적 기능을 도입하고 있습니다.

✅ 예: value class (inline)

@JvmInline
value class Email(val value: String)
  • 단일 필드에 최적화된 클래스
  • 객체 생성 비용 줄이고, 타입 안정성 향상

✅ 예: data object

data object Loading : UiState()
  • 싱글톤 형태로 값 객체를 표현할 수 있음

⚙️ Kotlin 2.0.20의 변경사항: copy() 가시성 통일

Kotlin 2.0.20부터는 Data Class의 copy() 함수가 생성자와 동일한 가시성을 갖도록 점진적으로 변경됩니다.

기존에는 private constructor를 가진 클래스라도 copy()는 public 함수로 생성되어, 클래스 외부에서 복사가 가능했어요..

❗ 문제 예시 (2.0.20부터 경고 발생)

data class PositiveInteger private constructor(val number: Int) {
    companion object {
        fun create(number: Int): PositiveInteger? =
            if (number > 0) PositiveInteger(number) else null
    }
}

val value = PositiveInteger.create(42) ?: return
val copied = value.copy(number = -1) // ⚠️ 경고 발생함

// ⚠️ 경고문구: Non-public primary constructor is exposed via the generated 'copy()' method of the 'data' class.

🔧 해결 방법

Kotlin 2.0.20에서는 두 가지 새로운 annotation이 도입되어 동작을 제어할 수 있습니다.

  • @ConsistentCopyVisibility : 향후 기본값이 될 "가시성 일치" 동작을 미리 적용
  • @ExposedCopyVisibility : 기존 동작을 유지하면서 경고 억제 (단, 컴파일러는 경고를 계속 표시함)

🛠 전체 모듈에 적용하기

-Xconsistent-data-class-copy-visibility

위 컴파일러 옵션을 설정하면 모든 데이터 클래스에 @ConsistentCopyVisibility가 적용된 것과 동일한 효과를 냅니다.

✅ 마치며

  • Kotlin의 Data Class는 값 중심 객체를 표현할 때 매우 효과적입니다.
  • equals, hashCode, toString, copy, componentN 등 실용적인 메서드를 자동으로 생성합니다.
  • 사용 시 제약 조건을 주의하면서, 적재적소에 사용하면 코드 가독성과 유지보수성이 크게 향상됩니다.
  • 특히 ViewModel 상태 관리, API 응답 처리, 도메인 모델링에 적극 활용할 수 있습니다

✉️ 레퍼런스

https://kotlinlang.org/docs/data-classes.html
https://kotlinlang.org/docs/whatsnew2020.html

profile
Android Developer

0개의 댓글