Kotlin - Data class理解と実装方法

By JS | Last updated: July 11, 2019

データクラス(Data class)は、データ保管の目的で作成されたクラスを指します。データクラスは、プロパティの toString()hashCode()equals()copy()メソッドを自動的に作成します。だからboilerplate codeを作らなくてもされます。

データクラスは、クラスの前に dataを付けます。たとえば、次のようにデータクラスを定義することができます。

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

Javaで変換してみると、基本的な 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 (name1, name2) = Data class objectのように括弧の中に変数を宣言してくれれば コンストラクタで定義されたプロパティが代入されます。

動作原理

事実Destructuring Declarationがどのように動作するのかを知る必要はないが、(Javaプログラマなら)鼻間違ったコードをJavaに変換し、どのように動作するか見てみると、より深く理解するのに役立つ可能性があります。

上記の例で使用されたコードをJavaに変換してみると次のように鼻間違ったでは定義していない componentN()メソッドが生成されます。

public final class Site {
    public Site(String url, String title) {
        this.url = url;
        this.title = title;
        this.description = "";
    }

    public final String component1() {
        return this.url;
    }

    public final String component2() {
        return this.title;
    }
    ...
}

ここで生成された componentN()の戻り値は、url、title変数に代入になります。

public final class Example {

   public static final void main(@NotNull String[] args) {
      Site site1 = new Site("kotlinlang.com", "Kotlin New Features (1)");
      String url = site1.component1();
      String title = site1.component2();
   }
}

再度まとめると、 Destructuring Declarationは、コンパイル時にcomponentN()メソッドを自動生成し、宣言した変数に戻り値を入れてくれるように変換されます。

注意点

Destructuring Declarationは、コンストラクタで定義されたプロパティの順に変数に代入します。

たとえば、次のコードでは、クラスのコンストラクタのプロパティはurl、title順序で定義がされてい宣言した変数は、title、url順序で定義されました。

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

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

val (title, url) = site1
// 各変数には、次の値が代入
// title = "kotlinlang.com"
// url = "Kotlin New Features (1)"

順序が後変わっても、コンパイラが知って正しい値を入れて与えるようだが、結果を見ると、実際にはそうではありません。

コンパイル時に、コンストラクタで定義された引数を返す componentN()メソッドが生成され、ここでの数字は、引数の順序を意味します。 つまり、最初の引数を返すメソッドは component1()に定義されます。 したがって、最初に宣言した変数titleに component1()の戻り値が、二番目に宣言した変数urlに component2()の戻り値が代入されます。

もし順序が異なる場合、コンパイルエラーも発生した場合いいのですが、タイプがすべてStringので、コンパイルもよくなります。 だから、常に順番に注意を払う必要があります。

一般的なクラスでDestructuring Declarations使用方法

Destructuring Declarationsは通常のクラスでも使用することができます。しかし、 componentN()メソッドを直接実装してくれるとします。 詳細はKotlin - Destructuring Declarationでご確認ください。

まとめ

データクラスが何であるかについて調べてみました。一般的に使用されるメソッドが自動的に生成されるためboilerplateコードを記述する必要がありません。 だから、データを保存する目的のクラスで使用するお勧めします。また、データクラスはDestructuring Declarationsをサポートするため、コードを簡潔で読みやすいようにしてくれます。

参考

Related Posts

codechachaCopyright ©2019 codechacha