2017年7月14日金曜日

Kotlin メモ : data class で List はいい感じに処理してくれるけど Array おまえはダメだ

data class A と B と C があって、C は A の配列と B を持っています。 data class A(val name: String) data class B(val age: Int) data class C(val names: Array<A>, val age: B) A と B に対する以下のテストは通ります assertEquals(A("hoge"), A("hoge")) assertEquals(B(10), B(10)) まぁ、そうだよね。

C に対する以下のテストは通りません assertEquals(C(arrayOf(A("hoge")), B(10)), C(arrayOf(A("hoge")), B(10))) data class がいい感じにやってくれるのかと思っていたのよ。やってくれなかった。

警告が出るのはそういうことなのね。



override すればいいんだけど... いけてない class C(val names: Array<A>, val age: B) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other?.javaClass != javaClass) return false other as C if (!Arrays.equals(names, other.names)) return false if (age != other.age) return false return true } override fun hashCode(): Int { var result = Arrays.hashCode(names) result = 31 * result + age.hashCode() return result } } Array で持つのやめて List にしたらどうかなと思って試したら data class A(val name: String) data class B(val age: Int) data class C(val names: List<A>, val age: B) assertEquals(C(listOf(A("hoge")), B(10)), C(listOf(A("hoge")), B(10))) 通った


結論: Array はダメだが List ならいい感じにやってくれる


Kotlin メモ : data class を継承できないので interface で実現した話

データクラス的な A と B があります。B は A を継承しています。 public class A { @NonNull public final String name; public A(@NonNull String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; A a = (A) o; return name.equals(a.name); } @Override public int hashCode() { return name.hashCode(); } } public class B extends A { public final int size; public B(String name, int size) { super(name); this.size = size; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; B b = (B) o; return size == b.size; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + size; return result; } } test はこんな感じです。B が A を継承しているのは、A のリストに B を入れたいからです。 public class ABTest { @Test public void test() { final B b = new B("hoge", 10); assertEquals("hoge", b.name); assertEquals(10, b.size); assertEquals(new B("hoge", 10), new B("hoge", 10)); assertNotEquals(new B("hoge", 10), new B("fuga", 10)); assertNotEquals(new B("hoge", 10), new B("hoge", 11)); List<A> aList = new ArrayList<>(); aList.add(new B("hoge", 10)); assertEquals(new B("hoge", 10), aList.get(0)); } } こういう Java コードがあって、これを Kotlin 化するときに A, B それぞれを data class にしようとしたら、data class は親クラスになれない(final)ので困りました。
このテストが通るように、いい感じに Kotlin 化したいのです。

1. data class にするのを諦める

自分で equals と hasCode を override すれば data class にしなくてもやりたいことはできます。
以下は自動変換しただけのもの。 open class A(val name: String) { override fun equals(o: Any?): Boolean { if (this === o) return true if (o == null || javaClass != o.javaClass) return false val a = o as A? return name == a!!.name } override fun hashCode(): Int { return name.hashCode() } } class B(name: String, val size: Int) : A(name) { override fun equals(o: Any?): Boolean { if (this === o) return true if (o == null || javaClass != o.javaClass) return false if (!super.equals(o)) return false val b = o as B? return size == b!!.size } override fun hashCode(): Int { var result = super.hashCode() result = 31 * result + size return result } } もちろん上のテストは通ります。
しかし、いけてない。

2. A を interface にする

Kotlin では interface に抽象プロパティを持たせることができるので、A を interface に変えてみます。 interface A { val name: String } こうすると、B を data クラスにできます。 data class B(override val name: String, val size: Int) : A めっちゃ短い!
テストもちゃんと通ります。



2017年7月13日木曜日

Kotlin メモ : Parcelable

Java public class Receipt implements Parcelable { @NonNull private final Date date; private final int value; public Receipt(@NonNull Date date, int value) { this.date = date; this.value = value; } @NonNull public Date getDate() { return date; } public int getValue() { return value; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Receipt receipt = (Receipt) o; if (value != receipt.value) return false; return date.equals(receipt.date); } @Override public int hashCode() { int result = date.hashCode(); result = 31 * result + value; return result; } // // Parcelable // @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeSerializable(date); dest.writeInt(value); } public static final Creator<Receipt> CREATOR = new Creator<Receipt>() { @Override public Receipt createFromParcel(Parcel in) { return new Receipt((Date) in.readSerializable(), in.readInt()); } @Override public Receipt[] newArray(int size) { return new Receipt[size]; } }; } Kotlin 自動変換直後 class Receipt(val date: Date, val value: Int) : Parcelable { override fun equals(o: Any?): Boolean { if (this === o) return true if (o == null || javaClass != o.javaClass) return false val receipt = o as Receipt? if (value != receipt!!.value) return false return date == receipt.date } override fun hashCode(): Int { var result = date.hashCode() result = 31 * result + value return result } // // Parcelable // override fun describeContents(): Int { return 0 } override fun writeToParcel(dest: Parcel, flags: Int) { dest.writeSerializable(date) dest.writeInt(value) } companion object { val CREATOR: Parcelable.Creator<Receipt> = object : Parcelable.Creator<Receipt> { override fun createFromParcel(`in`: Parcel): Receipt { return Receipt(`in`.readSerializable() as Date, `in`.readInt()) } override fun newArray(size: Int): Array<Receipt> { return arrayOfNulls(size) } } } } return arrayOfNulls(size) で型があわないと言われるので、newArray() の返す型を Array<Receipt?> にする

override fun newArray(size: Int): Array { return arrayOfNulls(size) } CREATOR が Java から見えるように @JvmField をつける companion object { @JvmField val CREATOR: Parcelable.Creator<Receipt> = object : Parcelable.Creator<Receipt> {


(Parcelable とは関係ないが) data クラスにして equals() と hashCode() を省略する data class Receipt(val date: Date, val value: Int) : Parcelable { override fun describeContents(): Int { return 0 } override fun writeToParcel(dest: Parcel, flags: Int) { dest.writeSerializable(date) dest.writeInt(value) } companion object { @JvmField val CREATOR: Parcelable.Creator<Receipt> = object : Parcelable.Creator<Receipt> { override fun createFromParcel(`in`: Parcel): Receipt { return Receipt(`in`.readSerializable() as Date, `in`.readInt()) } override fun newArray(size: Int): Array<Receipt?> { return arrayOfNulls(size) } } } } describeContents() と newArray() で = を使って関数の本体を省略 data class Receipt(val date: Date, val value: Int) : Parcelable { override fun describeContents() = 0 override fun writeToParcel(dest: Parcel, flags: Int) { dest.writeSerializable(date) dest.writeInt(value) } companion object { @JvmField val CREATOR: Parcelable.Creator<Receipt> = object : Parcelable.Creator<Receipt> { override fun createFromParcel(`in`: Parcel): Receipt { return Receipt(`in`.readSerializable() as Date, `in`.readInt()) } override fun newArray(size: Int): Array<Receipt?> = arrayOfNulls(size) } } } createFromParcel() の引数名に Java では in が使われているが、 Kotlin では予約語なので ` でエスケープされている。
紛らわしいので source に変える data class Receipt(val date: Date, val value: Int) : Parcelable { override fun describeContents() = 0 override fun writeToParcel(dest: Parcel, flags: Int) { dest.writeSerializable(date) dest.writeInt(value) } companion object { @JvmField val CREATOR: Parcelable.Creator<Receipt> = object : Parcelable.Creator<Receipt> { override fun createFromParcel(source: Parcel): Receipt { return Receipt(source.readSerializable() as Date, source.readInt()) } override fun newArray(size: Int): Array<Receipt?> = arrayOfNulls(size) } } } writeToParcel() の dest と createFromParcel() の source でスコープ関数を使う(お好みで) data class Receipt(val date: Date, val value: Int) : Parcelable { override fun describeContents() = 0 override fun writeToParcel(dest: Parcel, flags: Int) { dest.run { writeSerializable(date) writeInt(value) } } companion object { @JvmField val CREATOR: Parcelable.Creator<Receipt> = object : Parcelable.Creator<Receipt> { override fun createFromParcel(source: Parcel): Receipt = source.run { Receipt(readSerializable() as Date, readInt()) } override fun newArray(size: Int): Array<Receipt?> = arrayOfNulls(size) } } } run() を使っています。
他のスコープ関数(let, with, run, apply, also)を使うと次のようになります。

writeToParcel() ではどれを使ってもやりたいことはできます。
選ぶとしたら it が省略できる(そのレシーバを this とする関数を呼び出す) with か run か apply がよさそうです。 // let dest.let { it.writeSerializable(date) it.writeInt(value) } // with with(dest) { writeSerializable(date) writeInt(value) } // run dest.run { writeSerializable(date) writeInt(value) } // apply dest.apply { writeSerializable(date) writeInt(value) } // also dest.also { it.writeSerializable(date) it.writeInt(value) } createFromParcel() では、レシーバーの Parcel ではなく Receipt を返したいので apply, also は使えません。
ここでも選ぶとしたら it が省略できる with か run がよさそうです。 // let override fun createFromParcel(source: Parcel): Receipt = source.let { Receipt(it.readSerializable() as Date, it.readInt()) } // with override fun createFromParcel(source: Parcel): Receipt = with(source) { Receipt(readSerializable() as Date, readInt()) } // run override fun createFromParcel(source: Parcel): Receipt = source.run { Receipt(readSerializable() as Date, readInt()) } 65行が21行になった。