Kotlinのいいところ/Data Class

今回はKotlinシリーズの2回目、Data Classについて説明します。
Javaの「なんとなく面倒だな…」と思うところを補填してくれる機能なので、Kotlinを使うきっかけになればと思います。

Data Classとは

Data Classは簡単に言うと、データを保持することのみを目的としたクラスを簡単な定義で作成することができる機能です。
例えば、ユーザの名前と年齢を保持するクラスをJavaで簡単に書くとしたら、

public class User {
     public String name;
     public int age;
}

こんな感じでしょうか。
実際につかってみると、

fun main(args: Array<String>) {
    val user = User()

    user.name = "hoge"
    user.age = 10

    println("User object: ${user}")
    println("name: ${user.name}")
    println("age: ${user.age}")
}
User object: jp.co.techfirm.blog.User@4d7e1886
name: hoge
age: 10

というような形の結果になります。
ここでJavaで定義したUserクラスをData Classの機能を使って定義してみます。

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

定義はたったこれだけになりました。
実際に先程のJavaで作ったUserクラスを置き換える形でコンパイルしてみると、以下のようなエラーが出ます。

Error:(12, 21) Kotlin: No value passed for parameter name
Error:(12, 21) Kotlin: No value passed for parameter age
Error:(14, 5) Kotlin: Val cannot be reassigned
Error:(15, 5) Kotlin: Val cannot be reassigned

1,2行目のエラーはUserクラスのインスタンス作成時にname,ageの初期値が設定されていないため、3,4行目のエラーはUserクラスのname,ageをvalで定義しており代入不可となるためとなります。
ちょっとコードを編集して、実行できるようにします。

fun main(args: Array<String>) {
    val user = User("hoge", 10)

    println("User object: ${user}")
    println("name: ${user.name}")
    println("age: ${user.age}")
}
User object: User(name=hoge, age=10)
name: hoge
age: 10

実行結果がちょっと変わっていることに気付きましたか??
JavaのときはUser objectを表示させたときに「クラス名@ハッシュコード」の表記だったのですが、Data Classで定義した際はtoString()メソッドが自動的に作成されプロパティ値が表示されるようになっています。

その他にどんなメソッドが自動的に作成されているのかDecompilerを利用してみてみましょう。

import kotlin.data;
import kotlin.jvm.internal.Intrinsics;
import kotlin.jvm.internal.KotlinClass;
import org.jetbrains.annotations.NotNull;

@data
@KotlinClass(....)
public final class User
{
  @NotNull
  private final String name;
  private final int age;

  public boolean equals(Object paramObject)
  {
    if (this != paramObject)
    {
      if ((paramObject instanceof User))
      {
        User localUser = (User)paramObject;
        if (Intrinsics.areEqual(this.name, localUser.name)) {
          if ((this.age == localUser.age ? 1 : 0) == 0) {}
        }
      }
    }
    else {
      return true;
    }
    return false;
  }

  /* Error */
  public int hashCode()
  {
  }

  public String toString()
  {
    return "User(name=" + this.name + ", age=" + this.age + ")";
  }

  @NotNull
  public final User copy(@NotNull String name, int age)
  {
    Intrinsics.checkParameterIsNotNull(name, "name");
    return new User(name, age);
  }

  public final int component2()
  {
    return this.age;
  }

  @NotNull
  public final String component1()
  {
    return this.name;
  }

  public User(@NotNull String name, int age)
  {
    this.name = name;
    this.age = age;
  }

  public final int getAge()
  {
    return this.age;
  }

  @NotNull
  public final String getName()
  {
    return this.name;
  }
}

toString()以外にも、equals(),hashCode(),getterなどが自動生成されていますね。
それ以外にもcopy()とcomponent1(),component2()が定義されています。
これらのメソッドは何に使われるのでしょう。

まず、copyメソッドですが、これはインスタンスのデータをコピーし、新たなインスタンスを作成する際に用いられます。
KotlinではData Classで定義したクラスのインスタンスのコピーを以下のメソッドで取得できます。

val copyUser = user.copy()

この際に用いられるのが、先程decompileした際に生成されていたcopyメソッドとなります。
ちなみにdecompileしたcopyメソッドでは引数が必要となっていますが、Kotlin上ではcopyメソッドは以下の定義となるため、単純なコピーであれば引数は必要ありません。(引数がない場合はデフォルト引数として自分自身の持つ値が利用されます)

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

で、残ったcomponent1,component2メソッドですが、これらはKotlinのMulti-Declaration機能で用いられます。
KotlinのMulti-Declaration機能はどのようなものか、以下のコードを見てください。

    val user = User("hoge", 10)
    // 以下のコードがMulti-Declaration
    val (name, age) = user

この場合はuserインスタンスに含まれているデータを元にnameとageという変数を定義する形となります。
実際、どのような処理が行われているかをKotlinのコードで示すと、

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

という形になり、Data Class機能で生成されたcomponent1,component2というメソッドが利用されていることがわかります。
なお、自動生成されるcomponent1,component2,….,componentNメソッドで返される値は、KotlinのコンパイラによってData Classの定義時に定義したプロパティの順番にアサインされます。つまり data class User(val name: String, val age: Int) という定義の場合、component1はname、component2はageを返す形となります。

なお、実際にMulti-Decralation機能を用いる場合は上記コードのような使い方ではなく、メソッドの返り値で複数の値を返したい場合に使うことが多いと思います。


data class Result(val status: Int, val message: String)

fun sendData(data: String): Result {
    // 何かの処理....
    return Result(200, "OK")
}

fun main(args: Array<String>) {
    // Multi-Decralationを用いてメソッドから複数の値を受け取る
    val (status, message) = sendData("hoge")

    if (status == 200) {
        // ....
    } else {
        // ....
    }
}

今回はData Class機能について説明しました。
次回はExtension機能について説明したいと思います。(予定)

tetsuo

tetsuo の紹介

こう見えてテックファームの開発チームのえらい人。
カテゴリー: Android タグ: , パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です