HOME > kotlin > basic

Kotlin - Data class 이해 및 구현 방법

JSFollow11 Jul 2019

데이터 클래스(Data class)는 데이터 보관 목적으로 만든 클래스를 말합니다. 데이터 클래스는 프로퍼티에 대한 toString(), hashCode(), equals(), copy() 메소드를 자동으로 만들어 줍니다. 그래서 boilerplate code를 만들지 않아도 됩니다.

데이터 클래스는 클래스 앞에 data를 붙여줍니다. 예를 들어, 다음과 같이 데이터 클래스를 정의할 수 있습니다.

data class Site(val url: String, val title: String) {
    val description = ""
}

자바로 변환해서 보면, 기본적인 toString(), hashCode(), equals(), copy() 메소드가 구현된 것을 알 수 있습니다.

public final class Site {
   ....

   public Site(@NotNull String url, @NotNull String title) {
      ....
   }

   @NotNull
   public final Site copy(@NotNull String url, @NotNull String title) {
     return new Site(url, title);
   }

   @NotNull
   public String toString() {
      return "Site(url=" + this.url + ", title=" + this.title + ")";
   }

   public int hashCode() {
      return (this.url != null ? this.url.hashCode() : 0) * 31 + (this.title != null ? this.title.hashCode() : 0);
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Site) {
            Site var2 = (Site)var1;
            if (Intrinsics.areEqual(this.url, var2.url) && Intrinsics.areEqual(this.title, var2.title)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

toString()의 인자를 보면 생성자에서 선언한 프로퍼티만 있습니다. 따라서 지역변수에 대한 것은 고려되지 않습니다.

데이터 클래스의 특징은 다음과 같습니다.

  • 데이터 클래스의 생성자(primary constructor)는 1개 이상의 프로퍼티를 선언되어야 합니다.
  • 데이터 클래스의 생성자 프로퍼티는 val 또는 var으로 선언해야 합니다.
  • 데이터 클래스에 abstract, open, sealed, inner 를 붙일 수 없습니다.
  • 클래스에서 toString(), hashCode(), equals(), copy()를 override하면, 그 함수는 직접 구현된 코드를 사용합니다.
  • 데이터 클래스는 상속받을 수 없습니다.

copy()

copy()는 객체의 복사본을 만들어 리턴합니다. 리턴되는 객체는 얕은 복사(swallow copy)로 생성됩니다. copy()의 인자로 생성자에 정의된 프로퍼티를 넘길 수 있으며, 그 프로퍼티의 값만 변경되고 나머지 값은 동일한 객체가 생성됩니다.

아래 코드는 site1의 객체를 복사할 때 title만 변경하여 새로운 객체를 생성하는 예제입니다.

val site1 = Site("kotlinlang.com",
    "Kotlin New Features (1)")
val site2 = site1.copy(title = "Kotlin New Features (2)")

println(site1)
println(site2)

두 객체를 출력해보면 title만 변경되어 copy가 된 것을 볼 수 있습니다.

Site(url=kotlinlang.com, title=Kotlin New Features (1))
Site(url=kotlinlang.com, title=Kotlin New Features (2))

주의할 점은 copy()에 전달되는 인자는 생성자에 정의된 프로퍼티만 될 수 있습니다.

toString(), hashCode(), equals()

자동으로 구현된 toString(), hashCode(), equals() 함수를 호출해보겠습니다.

toString()은 생성자에 정의된 프로퍼티만 출력하고, 클래스 내에 지역변수로 선언한 프로퍼티는 출력하지 않습니다. 지역변수도 toString()에 출력하고 싶으면 직접 오버라이드해서 구현해줘야 합니다.

아래 코드는 3개의 메소드를 모두 사용하는 예제입니다.

val site1 = Site("kotlinlang.com",
    "Kotlin New Features (1)")
val site2 = site1.copy(title = "Kotlin New Features (2)")

println(site1.toString())
println(site1.hashCode())
println(site2.toString())
println(site2.hashCode())

if (site1.equals(site2)) {
    println("Eqaul")
} else {
    println("Not Eqaul")
}

실행해보면 다음과 같습니다. 모두 다른 hashCode를 출력하고, 다른 객체로 취급되고 있습니다.

Site(url=kotlinlang.com, title=Kotlin New Features (1))
-2144111637
Site(url=kotlinlang.com, title=Kotlin New Features (2))
-2144111606
Not Eqaul

데이터 분해 및 대입(Destructuring Declarations)

Destructuring Declarations라는 개념이 있습니다. Site객체의 내부 변수를 다른 변수에 대입하려면 일반적으로 다음과 같이 해야 합니다.

val site1 = Site("kotlinlang.com", "Kotlin New Features (1)")
val url = site1.url
val title = site1.title

하지만 데이터 클래스는 Destructuring Declarations를 지원하기 때문에, 아래 코드처럼 한줄로 표현할 수 있습니다.

val site1 = Site("kotlinlang.com", "Kotlin New Features (1)")

val (url, title) = site1
// 각 변수에 다음 값들이 대입됨
// url = "Kotlin New Features (1)"
// title = "kotlinlang.com"

val/var (생성자_프로퍼티_이름1, 생성자_프로퍼티_이름2) = 데이터 클래스 객체처럼 괄호 안에 변수를 선언해주면 생성자에 정의된 프로퍼티 이름과 동일한 객체가 대입됩니다.

주의할 점은 변수의 이름은 생성자에 정의된 이름과 동일해야 합니다. 동일한 이름을 매칭하여 값을 대입하기 때문에 변수의 순서가 바뀌어도 괜찮습니다.

아래 코드는 위와 다르게 변수의 순서가 바뀌었습니다. 하지만 동일한 동작을 합니다.

val site1 = Site("kotlinlang.com", "Kotlin New Features (1)")

val (title, url) = site1
// 각 변수에 다음 값들이 대입됨
// title = "kotlinlang.com"
// url = "Kotlin New Features (1)"

데이터 클래스의 Destructuring Declarations는 코드를 간단하게 만들고 읽기 쉽게 만들어줍니다.

정리

데이터 클래스가 무엇인지에 대해서 알아보았습니다. 일반적으로 사용하는 함수들이 자동으로 생성되기 때문에 boilerplate 코드를 작성할 필요가 없습니다. 그래서 데이터만 저장하는데 사용되는 클래스를 만들 때 데이터 클래스로 정의하면 좋습니다. 또한 데이터 클래스는 Destructuring Declarations를 지원하기 때문에 코드를 간결하고 가독성있게 만들어줍니다.

참고