안드로이드 개발자 노트
[이펙티브 코틀린] Item37. 데이터 집합 표현에 data 한정자를 사용하라 본문
반응형
때로는 데이터를 한꺼번에 전달해야 할 때가 있는데, 일반적으로 이러한 상황에서는 data 클래스를 사용합니다.
data class Player(
val id: Int,
val name: String,
val points: Int
)
val player = Player(0, "Gecko", 9999)
data 한정자를 붙이면, 몇 가지 함수가 자동으로 생성됩니다.
- toString: 클래스 이름과 기본 생성자 형태로 모든 프로퍼티와 값을 출력
- equals: 기본 생성자의 프로퍼티가 같은지 확인
player == Player(0, "Gecko", 9999) // true
player == Player(0, "Ross", 9999) // false
- hashCode: equals와 같은 결과를 낸다.
- copy: immutable 데이터 클래스를 만들 때 편리하다. copy는 기본 생성자 프로퍼티가 같은 새로운 객체를 복제한다. 새로운 객체의 값은 이름 있는 아규먼트를 활용해서 변경 가능하다. 또한 copy는 객체를 얕은 복사한다.
val newObj = player.copy(name = "Thor")
print(newObj) // Player(id=0, name=Thor, points=9999)
// 'Player' 데이터 클래스의 'copy'는 이런 형태로 만들어진다.
fun copy(
id: Int = this.id,
name: String = this.name,
points: Int = this.points
) = Player(id, name, points)
- componentN (component1, component2 ...): 위치를 기반으로 객체를 해제할 수 있게 한다.
val id: Int = player.component1() // 0
val name: String = player.component2() // "Gecko"
val pts: Int = player.component3() // 9999
// Map.Entry 등의 원하는 형태로도 객체를 해제할 수 있다.
val trip = mapOf(
"China" to "Tianjin",
"Russia" to "Petersburg",
"India" to "Rishikesh"
)
for ((country, city) in trip) {
println("we loved $city in $country")
}
// 위치 순서를 혼동하면 안되니 주의하자.
data class FullName(
val firstName: String,
val secondName: String,
val lastName: String
)
val elon = FullName("Elon", "Reeve", "Musk")
val (name, surname) = elon
print("It is $name $surname") // It is Elon Reeve
// 값을 하나만 갖는 데이터 클래스를 해제할 경우, 특히 람다 표현식과 함께 활용할 때 혼란을 줄 수 있다.
data class User(val name: String)
fun main() {
val user = User("John")
user.let { a -> print(a) } // User(user=John)
user.let { (a) -> print(a) } // John
}
튜플 대신 데이터 클래스 사용하기
코틀린의 튜플은 Serializable을 기반으로 만들어지며, toString을 사용할 수 있는 제네릭 데이터 클래스입니다.
public data class Pair<out A, out B> (
public val first: A,
public val second: B
) : Serializable {
public override fun toString(): String =
"($first, $second)"
}
public data class Triple<out A, out B, out C>(
public val first: A,
public val second: B,
public val third: C
) : Serializable {
public override fun toString(): String =
"($first, $second, $third)"
}
Pair와 Triple은 코틀린에 남아 있는 마지막 튜플입니다.
튜플은 과거에 (Int, String, String, Long)처럼 괄호와 타입 지정을 통해 원하는 형태의 튜플을 정의할 수 있었습니다.
튜플은 데이터 클래스와 같은 역할을 하지만, 튜플만 보고는 어떤 타입을 나타내는지 예측할 수 없어 점차 없어져 지역적인 목적으로 인해 Pair와 Triple만 남았습니다.
// 값에 간단하게 이름을 붙일 때
val (description, color) = when {
degrees < 5 -> "cold" to Color.BLUE
degrees < 23 -> "mild" to Color.YELLOW
else -> "hot" to Color.RED
}
// 미리 알 수 없는 aggregate(집합)을 표현할 때
val (odd, even) = numbers.partition { it % 2 == 1 }
val map = mapOf(1 to "San Francisco", 2 to "Amsterdam")
데이터 클래스를 활용하면 함수를 더 명확하게 만들어 줍니다.
- 함수의 리턴 타입이 더 명확해진다.
- 리턴 타입이 더 짧아지며, 전달하기 쉬워진다.
- 사용자가 데이터 클래스에 적혀 있는 것과 다른 이름을 활용해 변수를 해제하면, 경고가 출력된다.
- 좁은 스코프를 갖고 싶다면, private을 붙여 주면 된다.
예를 들어, 전체 이름(fullname)을 이름(name)과 성(surname)으로 분할하는 코드가 있을 때, 이름과 성을 Pair<String, String>으로 나타냈다고 해보겠습니다.
fun String.parseName(): Pair<String, String>? {
/* */
}
// 사용
val fullName = "Marcin Mosckala"
val (fisrtName, lastName) = fullName.parseName() ?: return
Pair<String, String>이 전체 이름을 나타낸다는 것을 인지하기 어려우며, 성(lastName)과 이름(firstName) 중에 어떤 것이 앞에 있을지 예측하기 어렵습니다.
data class FullName(
val firstName: String,
val lastName: String
)
fun String.parseName(): FullName? {
/* */
}
// 사용
val fullName = "Marcin Mosckala"
val (fisrtName, lastName) = fullName.parseName() ?: return
데이터 클래스를 활용하면 이를 해결할 수 있습니다.
정리
- 데이터 클래스를 활용하면, 튜플을 활용할 때보다 더 많은 장점이 있다.
- 코틀린에서 클래스는 큰 비용없이 사용할 수 있는 좋은 도구이다.
- 클래스를 활용하는 데 두려움을 갖지 말고, 적극 활용하라.
반응형
'Kotlin > 이펙티브 코틀린' 카테고리의 다른 글
[이펙티브 코틀린] Item38. 연산 또는 액션을 전달할 때는 인터페이스 대신 함수 타입을 사용하라 (0) | 2023.12.31 |
---|---|
[이펙티브 코틀린] Item36. 상속보다는 컴포지션을 사용하라 (0) | 2023.12.30 |
[이펙티브 코틀린] Item35. 복잡한 객체를 생성하기 위한 DSL을 정의하라 (0) | 2023.12.25 |