데이터를 한꺼번에 전달해야 할 때 data class
를 쓰는데, class 앞에 data
한정자를 붙이면 몇 가지 함수가 자동으로 생성된다.
toString 함수는 클래스의 이름과 기본 생성자 형태로 모든 프로퍼티와 값을 출력해준다. 그래서 로그를 출력할 때나 디버그할 때 유용하다.
equals는 기본 생성자의 프로퍼티가 같은지 확인해준다. hashCode도 같은 결과를 낸다.
copy는 기본 생성자 프로퍼티가 같은 새로운 객체를 복제한다.
copy메서드는 data 한정자를 붙이기만 하면 자동으로 만들어져서 구현을 볼수도 볼필요도 없는데 대충 다음과 같을 것이다
fun copy(
id: Int = this.id,
name: String = this.name,
points: Int = this.points
) = User(id, name, points)
copy 메서드는 객체를 얕은 복사하지만 이것은 객체가 immutable이라면 상관없다. 어차피 immutable 객체는 깊은 복사한 객체가 필요 없기 때문이다.
componentN 함수는 위치를 기반으로 객체를 해제한다.
val User = User(1, "Tom", 3452)
val (id, name, points) = user
componentN 함수만 있다면 List와 Map.Entry 등의 원하는 형태로도 객체를 해제할 수 있다.
하지만 위치를 잘못 지정하면 다양한 문제가 발생할 수 있어 위험하다.
그래서 데이터 클래스의 기본 생성자에 붙어 있는 프로퍼티 이름과 같은 이름을 사용하자.
참고로 값을 하나만 갖는 데이터클래스는 해제하지 않는 것이 좋다. 읽는 사람에게 혼동을 줄 수 있기 때문이다. 특히 람다 표현식에서 그렇다
ex)
data class user(val name: String)
fun main() {
val user = User("Tom")
user.let { a -> print(a) } // User(name=Tom)
//don't do that
user.let { (a) -> print(a) } // Tom
}
일부 프로그래밍 언어에서는 아규먼트 주변에 괄호를 입력해도 되거나 하지 않아도 되므로 문제다.
데이터 클래스는 튜플보다 많은 것을 제공한다. 코틀린의 튜플은 Serializable
을 기반으로 만들어지며 toString을 사용할 수 있는 제네릭 데이터 클래스다.
현재 코틀린에는 Pair
, Triple
2개의 튜플만 남아있다.
튜플은 좋아보였지만 어떤 타입을 나타내는지 예측할 수 없었기 때문에 데이터를 사용하는 것이 선호되었고 점차 없어지게 됐다.
위의 2개는 몇 가지 목적으로 인해 남아 있다.
val (desc, color) = when {
degrees < 5 -> "cold" to Color.BLUE
else -> "hot" to Color.Red
}
val (odd, even) = numbers.partition { it % 2 == 1 }
val map = mapOf(1 to "San Francisco", 2 to "Amsterdam")
이를 제외하면 무조건 데이터 클래스를 사용하는 것이 좋다.
데이터 클래스를 사용하면,
이 클래스가 좁은 스콥을 갖게 하고 싶으면 private
을 붙여 가시성에 제한을 걸면 된다.
코틀린에서 클래스는 큰 비용없이 사용할 수 있는 좋은 도구다. 따라서 클래스를 활용하는데 두려움을 갖지 말고, 적극적으로 활용하자