[Kotlin] Data Class

이동건·2023년 4월 20일

kotlin

목록 보기
1/1
post-thumbnail

Kotlin에서 Data Class데이터 저장에 최적화된 클래스이다. 컴파일러는 자동으로 주 생성자에 선언된 모든 속성에서 다음 함수들을 자동으로 생성한다.

  • equals() / hashCode()
  • toString()
  • componentN()
  • copy()

위 함수들이 일관성있고 의미있는 동작들을 하기 위해서는, 데이터 클래스는 다음 요구사항들을 만족해야 한다.

  • 주 생성자에는 최소 하나의 매개변수가 존재해야 한다.
  • 모든 주 생성자의 매개변수가 val or var로 표시되어야 한다.
  • 데이터 클래스는 abstrat, open, sealed or inner가 될 수 없다.

해당 사항들을 기억하고 컴파일러가 생성한 함수 각각에 대해 알아보자.


equals() / hashCode()

equals() 는 두 객체를 비교를 위한 메서드이다. 자바에서 두 객체의 주소 값을 비교하는 연산자가 == 이고 두 개의 값 자체를 비교하는 메서드가 equals()이지만, 코틀린에선 == 와 주소값 비교를 위해선 ===를 사용한다.

즉, 두 객체의 동등성 연산(같은 프로퍼티를 가지는지)을 위해 코틀린에선 == 를 사용하고, 이 연산자는 내부적으로 equals()를 호출한다. 데이터 클래스를 선언하면, 컴파일러가 data class의 모든 프로퍼티가 동등한 경우에만 두 객체가 동등하다고 판단하는 equals()메서드를 자동으로 생성한다.

val person1 = Person("John", 30)
val person2 = Person("John", 30)

// val isEqual = person1.equals(person2) 
// 아래의 식은 위와 동일합니다
val isEqual = person1 == person2 // true

hashCode()객체의 해시 코드, 즉 객체를 식별하는 하나의 정수값을 반환하는 메서드이다. 객체 내의 모든 프로퍼티가 같은 값을 같게 된다면, 동일한 해시 코드를 갖게 된다. 자바에서나 코틀린의 일반 클래스에서 이를 override하지 않으면, 서로 다른 두 객체 내의 모든 프로퍼티 값이 다 같더라도 다른 유니크 값을 반환한다.

hashcode 사용 이유

두 객체 내의 모든 프로퍼티 값이 같으면 equals()에서 true를 반환할텐데, 왜 hashcode를 사용할까요?
(출처) https://codechacha.com/ko/java-hashcode/

equals() 메서드는 객체를 비교하기 위해 모든 프로퍼티 값을 비교하기 때문에, 두 객체를 비교하는 데는 상당한 계산 비용이 들어갈 수 있다.

하지만 hashCode() 메서드는 객체의 해시코드를 반환하기 때문에, 동일한 프로퍼티 값을 가진다면 해시코드는 같은 값을 반환한다. 이를 이용하여 객체를 비교하면 equals로 비교한 것보다 시간이 단축된다. 보통 HashMap, HashTable과 같은 자료구조를 사용할 때 이를 이용하여 객체를 빠르게 검색할 수 있다.

따라서 객체를 비교할 때 먼저 hashcode를 이용하여 비교하면, 두 객체가 절대 같지 않은 경우를 빠르게 확인할 수 있다.

  • hashcode가 다르면, 두 개의 객체가 같지 않다.
  • hashcode가 같으면, 두 개의 객체가 같거나 다를 수 있다.

주의할 점

만약에 equals() 메서드를 오버라이드 한다면, hashCode() 메서드도 함께 오버라이드 해야한다. 둘 중 하나만 재정의 하게 되면, 두 객체가 같은 값들을 가지더라도 해시값이 다른 문제가 발생할 수 있다.


toString()

자바에서나 일반 클래스 객체에 toString()을 사용하기 위해서는 직접 구현하거나 오버라이딩을 해야했다. 그러나 데이터 클래스를 선언하면, 자동으로 toString() 메서드가 생성되고 이는 클래스 내의 모든 프로퍼티를 포함하는 문자열을 반환한다.

data class Person(val name: String, val age: Int)

// 자동 생성
override fun toString(): String {
    return "Person(name=$name, age=$age)"
}

해당 메서드는 객체를 디버깅하거나 내부 상태를 확인할 때 유용하다.


componentN()

이 메서드는 프로퍼티를 인덱스(1부터 시작)로 접근할 수 있게 해주며, 객체를 여러 변수로 분해할 수 있게 해준다. 예를 들면 다음과 같다.

val (name, age) = person

위와 같은 선언을 Destructuring 선언이라 한다. 이는 다음 코드로 컴파일된다.

val name = person.component1()
val age = person.component2()

실제로 컴파일된 코드 내부에는 다음과 같이 작성되어 있다.


copy()

copy() 메서드는 클래스 주 생성자의 데이터를 그대로 복사하여, 동일한 속성 값을 가지는 새로운 객체를 생성한다. 일부 속성만 변경하고 싶으면 해당 속성의 값을 인자로 전달하여, 해당 속성만 변경된 새로운 객체를 만들 수 있다.

val person1 = Person("John", 30)
val person2 = person1.copy()
val person3 = person1.copy(age = 40)

println(person1) // 출력: Person(name=John, age=30)
println(person2) // 출력: Person(name=John, age=30)
println(person3) // 출력: Person(name=John, age=40)

copy를 사용하는 이유

https://velog.io/@dddooo9/Kotlin-data-class-파헤치기-copy-toString-equals-hashcode-componentN

Data Class의 프로퍼티는 val, var 둘 다 선언이 가능하지만, 모두 읽기전용으로 val로 만들어 불변 클래스를 만드는 것이 좋다. 이유는 다음과 같다.

  • HashMap 등의 컨테이너에 객체를 담는 경우 불변성이 필수적이다.
  • 불변 객체는 추론이 쉽다.
  • 다중스레드 프로그램의 경우, 객체의 값이 변하면 스레드간 동기화를 해야한다.
  • 객체를 메모리상에서 직접 바꾸는 것보다 복사본을 만드는 편이 낫다.

참고

https://velog.io/@dddooo9/Kotlin-data-class-파헤치기-copy-toString-equals-hashcode-componentN

https://devhyeon0312.tistory.com/34

https://codechacha.com/ko/java-hashcode/

https://velog.io/@haero_kim/Kotlin-감동-실화-Data-Class-알아보기

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

profile
성장하는 활동적인 개발자

0개의 댓글