Slide 1

Slide 1 text

データクラスの話 スコープ関数の話 R.kt #1 2017-10-04 長澤太郎 @ngsw_taro

Slide 2

Slide 2 text

自己紹介 ● 長澤 太郎(たろーって呼んでね) ● @ngsw_taro ● エムスリー株式会社 ● ディズニーが大好き! エムスリーは 国内最大規模の 医療情報プラット フォームを開発・ 運営しています。 エンジニア募 集中!

Slide 3

Slide 3 text

祝☆新刊 10/6発売

Slide 4

Slide 4 text

もくじ 1. データクラスの基本のおさらい 2. データクラスの制限 3. スコープ関数runとlet 4. スコープ関数applyとalso

Slide 5

Slide 5 text

1. データクラスの基本のおさらい

Slide 6

Slide 6 text

データを表現するクラス class Name(val value: String) val taro = Name("太郎") taro.toString() //=> Name@1f32e575 taro == Name("太郎") //=> false

Slide 7

Slide 7 text

データを表現するクラス class Name(val value: String) val taro = Name("太郎") taro.toString() //=> Name@1f32e575 taro == Name("太郎") //=> false 同じ名前で、インスタンスを区 別する必要がないからtrueを 期待する 役に立ちそうにない 情報

Slide 8

Slide 8 text

データを表現するクラス class Name(val value: String) { ??? } val taro = Name("太郎") taro.toString() //=> Name(value='太郎') taro == Name("太郎") //=> true 欲しかったやつ!

Slide 9

Slide 9 text

データを表現するクラス class Name(val value: String) { ??? } val taro = Name("太郎") taro.toString() //=> Name(value='太郎') taro == Name("太郎") //=> true 欲しかったやつ!

Slide 10

Slide 10 text

ちゃんと実装してやればよいのです! class Name(val value: String) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Name if (value != other.value) return false return true } override fun hashCode(): Int = value.hashCode() override fun toString(): String = "Name(value='$value')" }

Slide 11

Slide 11 text

ちゃんと実装してやればよいのです! class Name(val value: String) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Name if (value != other.value) return false return true } override fun hashCode(): Int = value.hashCode() override fun toString(): String = "Name(value='$value')" } ・・・

Slide 12

Slide 12 text

データクラスが自動で提供してくれる data class Name(val value: String) val taro = Name("太郎") taro.toString() //=> Name(value='太郎') taro == Name("太郎") //=> true

Slide 13

Slide 13 text

イミュータブルに嬉しいcopyメソッド data class FullName(val firstName: String, val lastName: String) val nagasawa = FullName("太郎", "長澤") val urashima = nagasawa.copy(lastName = "浦島") urashima.firstName //=> 太郎 urashima.lastName //=> 浦島 nagasawa === urashima //=> false

Slide 14

Slide 14 text

イミュータブルに嬉しいcopyメソッド data class FullName(val firstName: String, val lastName: String) val nagasawa = FullName("太郎", "長澤") val urashima = nagasawa.copy(lastName = "浦島") urashima.firstName //=> 太郎 urashima.lastName //=> 浦島 nagasawa === urashima //=> false 名前付き引数で、必要なものだけ変更する

Slide 15

Slide 15 text

イミュータブルに嬉しいcopyメソッド data class FullName(val firstName: String, val lastName: String) val nagasawa = FullName("太郎", "長澤") val urashima = nagasawa.copy(lastName = "浦島") urashima.firstName //=> 太郎 urashima.lastName //=> 浦島 nagasawa === urashima //=> false lastNameだけ変更されている

Slide 16

Slide 16 text

イミュータブルに嬉しいcopyメソッド data class FullName(val firstName: String, val lastName: String) val nagasawa = FullName("太郎", "長澤") val urashima = nagasawa.copy(lastName = "浦島") urashima.firstName //=> 太郎 urashima.lastName //=> 浦島 nagasawa === urashima //=> false 元のインスタンスとは別物

Slide 17

Slide 17 text

地味に便利なcomponentNメソッド data class FullName(val firstName: String, val lastName: String) val nagasawa = FullName("太郎", "長澤") val (first, last) = nagasawa first //=> 太郎 last //=> 長澤

Slide 18

Slide 18 text

地味に便利なcomponentNメソッド data class FullName(val firstName: String, val lastName: String) val nagasawa = FullName("太郎", "長澤") val (first, last) = nagasawa first //=> 太郎 last //=> 長澤 分解宣言

Slide 19

Slide 19 text

地味に便利なcomponentNメソッド data class FullName(val firstName: String, val lastName: String) { operator fun component1(): String = firstName operator fun component2(): String = lastName } val nagasawa = FullName("太郎", "長澤") val (first, last) = nagasawa first //=> 太郎 last //=> 長澤 データクラスがこのようなメソッド実装を自動生成する

Slide 20

Slide 20 text

2. データクラスの制限

Slide 21

Slide 21 text

[ver1.1より前] クラスを継承できなかった sealed class Exp { class Number(val value: Int): Exp() class Addition(val x: Exp, val y: Exp): Exp() } val exp: Exp = Addition(Number(1), Number(2)) val sum: Number = eval(exp) sum //=> Exp$Number@2ff4acd0

Slide 22

Slide 22 text

[ver1.1より前] クラスを継承できなかった sealed class Exp { class Number(val value: Int): Exp() class Addition(val x: Exp, val y: Exp): Exp() } val exp: Exp = Addition(Number(1), Number(2)) val sum: Number = eval(exp) sum //=> Exp$Number@2ff4acd0 dataを付けるとコンパイルエラーとなっていた

Slide 23

Slide 23 text

[ver1.1以降] クラスの継承OK sealed class Exp { data class Number(val value: Int): Exp() data class Addition(val x: Exp, val y: Exp): Exp() } val exp: Exp = Addition(Number(1), Number(2)) val sum: Number = eval(exp) sum //=> Number(value=3)

Slide 24

Slide 24 text

データクラスを継承するのはNG open data class Name(val value: String) data class NameWithRuby(val value: String, val ruby: String): Name(value) 同時にマークすることはできない

Slide 25

Slide 25 text

データクラスを継承するのはNG open data class Name(val value: String) data class NameWithRuby(val value: String, val ruby: String): Name(value) ● 実装の継承はあまりよろしくない ● できたとしてcopyやcomponentNにおかし なことが起こるか大きな制限がかかる ● all-openコンパイラプラグインで継承可能。 ただしサブクラスはデータクラスにできない。

Slide 26

Slide 26 text

データクラスを継承したくなったら data class Name(val value: String) data class NameWithRuby(val value: String, val ruby: String) { fun toName(): Name = Name(value) } interface StringWrapper { val value: String fun component1(): String } data class Name(override val value: String): StringWrapper data class NameWithRuby(override val value: String, val ruby: String): StringWrapper このどちらか、 または両方で対処

Slide 27

Slide 27 text

3. スコープ関数runとlet

Slide 28

Slide 28 text

スコープ関数 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply this 対象オブジェクト also it 対象オブジェクト with this 指定できる

Slide 29

Slide 29 text

スコープ関数 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply this 対象オブジェクト also it 対象オブジェクト

Slide 30

Slide 30 text

runとlet 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply this 対象オブジェクト also it 対象オブジェクト ● 対象オブジェクトを変換する ● 対象オブジェクトについて副作用を起こす

Slide 31

Slide 31 text

対象オブジェクトを変換する ● 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... } val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null

Slide 32

Slide 32 text

対象オブジェクトを変換する ● 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... } val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null NotNullを取る関数

Slide 33

Slide 33 text

対象オブジェクトを変換する ● 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... } val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null Nullableな変数

Slide 34

Slide 34 text

対象オブジェクトを変換する ● 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... } val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null 安全呼び出しが使えないからnullチェックを

Slide 35

Slide 35 text

対象オブジェクトを変換する ● 主にNullableな参照に対して使用される fun eval(exp: Exp): Number { ... } val exp: Exp? = readExpression() val sum: Number? = if(exp != null) eval(exp) else null val sum: Number? = exp?.let { eval(it) } letを使用する

Slide 36

Slide 36 text

対象オブジェクトについて副作用を起こす listAdapter.items.clear() listAdapter.items.addAll(newItems) listAdapter.notifyDataSetChanged()

Slide 37

Slide 37 text

対象オブジェクトについて副作用を起こす listAdapter.items.clear() listAdapter.items.addAll(newItems) listAdapter.notifyDataSetChanged() 対象オブジェクトのメソッドを呼び出して、いろいろ設定

Slide 38

Slide 38 text

対象オブジェクトについて副作用を起こす listAdapter.items.clear() listAdapter.items.addAll(newItems) listAdapter.notifyDataSetChanged() listAdapter.run { items.clear() items.addAll(newItems) notifyDataSetChanged() } runを使用する

Slide 39

Slide 39 text

対象オブジェクトについて副作用を起こす listAdapter.items.clear() listAdapter.items.addAll(newItems) listAdapter.notifyDataSetChanged() listAdapter.run { items.clear() items.addAll(newItems) notifyDataSetChanged() } runを使用する 今回はrunなので、 このブロックの中で thisの意味が変わる。

Slide 40

Slide 40 text

対象オブジェクト thisかitか 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply this 対象オブジェクト also it 対象オブジェクト ● itの方を使っておけば無難 → 命名できる、文脈を維持 ● thisの方を使った方が楽な場面はある → ただし、ネストは避けること

Slide 41

Slide 41 text

4. スコープ関数applyとalso

Slide 42

Slide 42 text

applyとalso 対象オブジェクト 戻り値 run this 指定できる let it 指定できる apply this 対象オブジェクト also it 対象オブジェクト ● 対象オブジェクトの生成に伴う各種設定 ● fluent interface, メソッドチェーンの代替?

Slide 43

Slide 43 text

Android, Bundleクラスの理想と現実 val bundle: Bundle = Bundle() .putInt("one", 1) .putInt("two", 2) .putInt("three", 3) val bundle: Bundle = Bundle() bundle.putInt("one", 1) bundle.putInt("two", 2) bundle.putInt("three", 3) 【理想】 【現実】

Slide 44

Slide 44 text

Android, Bundleクラスの理想と現実 val bundle: Bundle = Bundle() .putInt("one", 1) .putInt("two", 2) .putInt("three", 3) val bundle: Bundle = Bundle() bundle.putInt("one", 1) bundle.putInt("two", 2) bundle.putInt("three", 3) 【理想】 【現実】 そこでapply

Slide 45

Slide 45 text

applyでBundleオブジェクト生成から設定 val bundle: Bundle = Bundle().apply { putInt("one", 1) putInt("two", 2) putInt("three", 3) }

Slide 46

Slide 46 text

applyでBundleオブジェクト生成から設定 val bundle: Bundle = Bundle().run { putInt("one", 1) putInt("two", 2) putInt("three", 3) this } val bundle: Bundle = Bundle().apply { putInt("one", 1) putInt("two", 2) putInt("three", 3) } runでも同じことができるが、自分 自身を返す必要がある。

Slide 47

Slide 47 text

スコープ関数 選択チャート あるオブジェクトに対して、 まとめて何か操作をした い! 対象オブジェクトへの 参照はNullableである。 戻り値として任意のオブジェ クトを指定したい。 あるいは興味がない。 thisの文脈が複雑である (内部クラスやレシーバ付き ラムダなど) 名前を付けたい。 let apply run also with 使う場面では ないかも? 人とは違うことをした い! 名前を付けたい。 thisの文脈が複雑である (内部クラスやレシーバ付き ラムダなど) アナタに合う スコープ関数が きっと見つかる! YES NO

Slide 48

Slide 48 text

まとめ ● データクラスにすると便利なメソッドが無料で手に入る ● [ver1.1] サブクラスをデータクラスにできるようになった ● データクラスをスーパクラスにはできないし、よいアイデアでは ない ● スコープ関数は、対象オブジェクトの見え方と戻り値によって 分類することができる ● runとletは変換、副作用に ● applyとalsoはオブジェクトの生成と設定を一括で