다음의 코틀린 코드가 있을 때, 결과값을 예상해보자.
class Person(val firstName: String, val lastName:String, val age: Int) {}
fun main() {
val person1 = Person("James", "Bond", 40)
val person2 = Person("James", "Bond", 40)
println(person1 == person2)
}
결과는 false이다. 왜 그럴까?
person1과 person2의 내용인 firstName, lastName, age가 모두 같지만 두 변수는 결국 다른 인스턴스를 가리키고 있기 때문이다.
만약 프로그래머가 Person 객체의 이름과 나이가 같다면 같은 객체라고 새롭게 정의하고 싶다면 equals() 와 hashCode() 함수를 override 해야한다.
그렇다면 equals()를 재정의하여 원하는대로 프로그램이 작동하게 만들어보자.
class Person(val firstName: String,
val familyName: String,
val age: Int) {
override fun equals(other: Any?): Boolean {
if (other !is Person) return false // 타입 비교
else {
return this.firstName == other.firstName && this.familyName == other.familyName && this.age == other.age
}
}
}
치밀하고 체계적이여서 빈틈이 없는 코드를 작성할 수 있지만 번거로운 것은 사실이다.
그런 번거로움을 덜어주는 것이 바로 data class 이다.
data class Person(val firstName: String, val lastName: String, val age: Int)
data 키워드를 붙여주면 자동으로 equals()와 hashCode()가 재정의되어 번거로움이 줄어든다. 단, 비교는 주생성자의 파라미터의 비교로 하기 때문에 그 점을 유의해야한다.
위와 같이 작성할 경우 toString() 메서드까지 지원해주는데 한번 확인해보자.
fun main() {
val person = Person("James", "Bond", 40)
println(person.toString()) //Person(firstName=James, familyName=Bond, age=40)
}
data class의 메서드로 copy() 메서드가 존재한다.
data class의 프로퍼티를 그대로 복사하여 새로운 인스턴스를 만든다.
물론 data class이기 때문에 둘의 동등성은 같다고 보장된다.
fun main() {
val person = Person("James", "Bond", 40)
println(person.toString())
person.show()
person.copy().show()
person.copy(firstName = "John").show()
person.copy(firstName = "Trevor", familyName = "Phillips", age = 45).show()
}
코틀린에서 기본적으로 지원하는 Pair와 Triple 데이터 클래스를 알아보자.
fun main() {
val pair = Pair(1, "two") // val pair = 1 to "two" 로 대체 가능
println(pair.first + 1) // 2
println("${pair.second} !") // two !
val triple = Triple("one", 2, false)
println("${triple.first} !") // one !
println(triple.second – 1) // 1
println(!triple.third) // true
}
data class Person(val firstName: String, val familyName: String, val age: Int)
fun main() {
val person = Person("John", "Doe", 25)
val (firstName, familyName, age) = person
// firstName, familyName, age 를 한번에 선언하였다.
if (age < 18) {
println("age is $age")
}
val (_, familyName2, age2) = person
// firstName을 생략하고 싶다면 언더바(_)를 활용하면 된다.
val (firstName3, familyName3) = person
// age를 생략하고 싶다면 변수의 선언을 생략하면 된다.
}
그럼 이 선언 방식을 어떻게 써먹을 수 있을까?
파이썬에서 enumerate 함수와 튜플의 조합을 생각해보자.
for (name, value) in enumerate(my_dictionary):
# do something...
pass
위 코드처럼 코틀린에서 활용가능하다.
val cities = arrayOf( Pair("Paris", "France"), Pair("Seoul", "Korea") )
fun main() {
for ((city, country) in cities) {
println("city: $city, country: $country")
}
}