Slide 1

Slide 1 text

クラスの作り方に見る Kotlinの表現力 2017-02-20 JJUGナイトセミナー 長澤 太郎 @ngsw_taro

Slide 2

Slide 2 text

Kotlinとは? ● Java仮想マシンをターゲットとしたプログラミング言語 ○ JavaScriptやAndroidもサポート ● IntelliJ IDEAでおなじみのJetBrainsが開発 ● 2011年に発表され、2016年2月にver1.0がリリース ● 現在 ver1.0.6, 1.1-beta2 ● 静的型付けオブジェクト指向言語 ● 簡潔、安全、Javaとの相互運用性

Slide 3

Slide 3 text

Hello World package sample fun main(args: Array) { if(args.isEmpty()) return val name = args[0] println("Hello, ${name}!") }

Slide 4

Slide 4 text

クラスの作り方に見る Kotlinの表現力

Slide 5

Slide 5 text

● 長澤 太郎 たろーって呼んでね ● @ngsw_taro 自己紹介

Slide 6

Slide 6 text

エムスリー株式会社 jobs.m3.com/engineer

Slide 7

Slide 7 text

エバンジェリストな私 ● Kotlin歴 5年 ● 日本Kotlinユーザグループ代表 ● 講演実績多数 ○ DroidKaigi 2015, 2016, 2017予定 ○ JJUG CCC 2015 Fall ○ 福岡、京都など遠征も ● 執筆実績多数 ○ 単行本、商業誌、同人誌

Slide 8

Slide 8 text

もくじ 1. 有理数 2. 片方向リストのノード

Slide 9

Slide 9 text

● 有理数クラス Rational ● プロパティ ○ 分子(numerator) ○ 分母(denominator) ● メソッド ○ いろんな計算を提供 題材その1: 有理数を表現するクラス

Slide 10

Slide 10 text

クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方 fun main(args: Array) { val half = Rational(1, 2) println(half.denominator) // 2 }

Slide 11

Slide 11 text

クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方 fun main(args: Array) { val half = Rational(1, 2) println(half.denominator) // 2 } クラス名

Slide 12

Slide 12 text

クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方 fun main(args: Array) { val half = Rational(1, 2) println(half.denominator) // 2 } プライマリコンストラクタ

Slide 13

Slide 13 text

クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方 fun main(args: Array) { val half = Rational(1, 2) println(half.denominator) // 2 } プロパティ

Slide 14

Slide 14 text

クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方 fun main(args: Array) { val half = Rational(1, 2) println(half.denominator) // 2 } インスタンス生成

Slide 15

Slide 15 text

クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方 fun main(args: Array) { val half = Rational(1, 2) println(half.denominator) // 2 } プロパティにアクセス: Javaで言うフィールドとアクセサが一緒になったようなもの

Slide 16

Slide 16 text

ちなみにJavaだと public final class Rational { private final int numerator; private final int denominator; public Rational(int numerator, int denominator) { this.numerator = numerator; this.denominator = denominator; } public int getNumerator() { return numerator; } public int getDenominator() { return denominator; } }

Slide 17

Slide 17 text

メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override fun toString() = "${numerator}/${denominator}" }

Slide 18

Slide 18 text

メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override fun toString(): String = "${numerator}/${denominator}" } オーバライドに必須

Slide 19

Slide 19 text

メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override fun toString(): String = "${numerator}/${denominator}" } イコールで式を結びつける

Slide 20

Slide 20 text

メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override fun toString(): String = "${numerator}/${denominator}" } 式が評価された文字列内に展開される = String Templates

Slide 21

Slide 21 text

確認しやすくなった! fun main(args: Array) { println(Rational(2, 5)) // 「2/5」 println(Rational(3, 0)) // 「3/0」 println(Rational(4, 10)) // 「4/10」 println(Rational(9, 3)) // 「9/3」 }

Slide 22

Slide 22 text

確認しやすくなった! fun main(args: Array) { println(Rational(2, 5)) // 「2/5」 println(Rational(3, 0)) // 「3/0」 println(Rational(4, 10)) // 「4/10」 println(Rational(9, 3)) // 「9/3」 } 分母ゼロを禁止したい!

Slide 23

Slide 23 text

イニシャライザ class Rational(val numerator: Int, val denominator: Int) { init { if(denominator == 0) throw IllegalArgumentException("ゼロはダメ") } override fun toString(): String = ... }

Slide 24

Slide 24 text

イニシャライザ class Rational(val numerator: Int, val denominator: Int) { init { if(denominator == 0) throw IllegalArgumentException("ゼロはダメ") } override fun toString(): String = ... } イニシャライザ

Slide 25

Slide 25 text

イニシャライザ class Rational(val numerator: Int, val denominator: Int) { init { require(denominator != 0, {"ゼロはダメ"}) } override fun toString(): String = ... } 標準関数

Slide 26

Slide 26 text

もう大丈夫!? fun main(args: Array) { println(Rational(2, 5)) // 「2/5」 println(Rational(3, 0)) // 例外 println(Rational(4, 10)) // 「4/10」 println(Rational(9, 3)) // 「9/3」 }

Slide 27

Slide 27 text

もう大丈夫!? fun main(args: Array) { println(Rational(2, 5)) // 「2/5」 println(Rational(3, 0)) // 例外 println(Rational(4, 10)) // 「4/10」 println(Rational(9, 3)) // 「9/3」 } 約分したい!

Slide 28

Slide 28 text

非公開メソッド class Rational(val numerator: Int, val denominator: Int) { ... private fun gcd(x: Int, y: Int): Int = if(y == 0) x else gcd(y, x % b) } xとyの最大公約数を返すメソッド

Slide 29

Slide 29 text

非公開メソッド class Rational(val numerator: Int, val denominator: Int) { ... private fun gcd(x: Int, y: Int): Int = if(y == 0) x else gcd(y, x % y) } if-elseは式(値を返す)

Slide 30

Slide 30 text

非公開メソッド class Rational(val numerator: Int, val denominator: Int) { ... tailrec private fun gcd(x: Int, y: Int): Int = if(y == 0) x else gcd(y, x % y) } 末尾再帰なので、最適化可能

Slide 31

Slide 31 text

非公開プロパティ class Rational(n: Int, d: Int) { init { require(d != 0, {"ゼロはダメ"}) } private val g: Int = gcd(n, d) val numerator: Int = n / g val denominator: Int = d / g ... }

Slide 32

Slide 32 text

非公開プロパティ class Rational(n: Int, d: Int) { init { require(d != 0, {"ゼロはダメ"}) } private val g: Int = gcd(n, d) val numerator: Int = n / g val denominator: Int = d / g ... } コンストラクタ引数(プロパティでない)

Slide 33

Slide 33 text

非公開プロパティ class Rational(n: Int, d: Int) { init { require(d != 0, {"ゼロはダメ"}) } private val g: Int = gcd(n, d) val numerator: Int = n / g val denominator: Int = d / g ... } 非公開プロパティ

Slide 34

Slide 34 text

非公開プロパティ class Rational(n: Int, d: Int) { init { require(d != 0, {"ゼロはダメ"}) } private val g: Int = gcd(n, d) val numerator: Int = n / g val denominator: Int = d / g ... } 公開プロパティ

Slide 35

Slide 35 text

すばらしい! fun main(args: Array) { println(Rational(2, 5)) // 「2/5」 println(Rational(3, 0)) // 例外 println(Rational(4, 10)) // 「2/5」 println(Rational(9, 3)) // 「3/1」 }

Slide 36

Slide 36 text

演算子オーバロード class Rational(n: Int, d: Int) { ... operator fun plus(r: Rational): Rational = Rational( numerator * r.denominator + r.numerator + denominator, denominator * r.denominator ) }

Slide 37

Slide 37 text

演算子オーバロード class Rational(n: Int, d: Int) { ... operator fun plus(r: Rational): Rational = Rational( numerator * r.denominator + r.numerator + denominator, denominator * r.denominator ) } 有理数との足し算メソッド

Slide 38

Slide 38 text

演算子オーバロード class Rational(n: Int, d: Int) { ... operator fun plus(r: Rational): Rational = Rational( numerator * r.denominator + r.numerator + denominator, denominator * r.denominator ) } +演算子を使ったメソッド呼び出しが可能に

Slide 39

Slide 39 text

演算子で計算できる! Rational(1, 6) + Rational(1, 3) //=> 1/2

Slide 40

Slide 40 text

オーバロード class Rational(n: Int, d: Int) { ... operator fun plus(r: Rational): Rational = ... operator fun plus(i: Int): Rational = Rational(numerator + i * denominator, denominator) }

Slide 41

Slide 41 text

演算子で計算できる! Rational(1, 6) + Rational(1, 3) //=> 1/2 Rational(1, 4) + 2 //=> 9/4

Slide 42

Slide 42 text

じゃあこれは? Rational(1, 6) + Rational(1, 3) //=> 1/2 Rational(1, 4) + 2 //=> 9/4 2 * Rational(2, 5) // ???

Slide 43

Slide 43 text

拡張関数 class Rational(n: Int, d: Int) { ... } operator fun Int.times(r: Rational): Rational = Rational(r.numerator * this, r.denominator)

Slide 44

Slide 44 text

拡張関数 class Rational(n: Int, d: Int) { ... } operator fun Int.times(r: Rational): Rational = Rational(r.numerator * this, r.denominator) Intの拡張関数(しかも演算子オーバロード)

Slide 45

Slide 45 text

できた!! Rational(1, 6) + Rational(1, 3) //=> 1/2 Rational(1, 4) + 2 //=> 9/4 2 * Rational(2, 5) // 4/5

Slide 46

Slide 46 text

● 片方向リストのノード Node ● プロパティ ○ 値(value) ○ 次のノードへのポインタ(next) 題材その2: 片方向リストのノード value: 111 next: value: 222 next: value: 333 next: nil Node.of(111, Node.of(222, Node.of(333, Node.nil)))

Slide 47

Slide 47 text

抽象クラス abstract class Node { abstract val value: T abstract val next: Node }

Slide 48

Slide 48 text

abstract class Node { abstract val value: T abstract val next: Node } 抽象クラス 抽象プロパティ

Slide 49

Slide 49 text

abstract class Node { abstract val value: T abstract val next: Node } 抽象クラス 共変指定 Java的には extends T>

Slide 50

Slide 50 text

継承してノードを表現 class PresentNode (override val value: T, override val next: Node): Node() { override fun toString(): String = "${value} -> ${next}" }

Slide 51

Slide 51 text

継承してノードを表現 class PresentNode (override val value: T, override val next: Node): Node() { override fun toString(): String = "${value} -> ${next}" } 継承 スーパクラスのコンストラクタ呼び出し

Slide 52

Slide 52 text

class PresentNode (override val value: T, override val next: Node): Node() { override fun toString(): String = "${value} -> ${next}" } 継承してノードを表現 プロパティのオーバライド 内部フィールド + そのアクセサを自動提供

Slide 53

Slide 53 text

継承してノードを表現 class PresentNode (override val value: T, override val next: Node): Node() { override fun toString(): String = "${value} -> ${next}" } ついでに 「1 -> 2 -> 3 -> X」のような表現を

Slide 54

Slide 54 text

(シングルトン)オブジェクト object AbsentNode: Node { override val value: Nothing get() = throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" }

Slide 55

Slide 55 text

(シングルトン)オブジェクト object AbsentNode: Node { override val value: Nothing get() = throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" }

Slide 56

Slide 56 text

(シングルトン)オブジェクト object AbsentNode: Node { override val value: Nothing get() = throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" } あらゆる型のサブタイプ

Slide 57

Slide 57 text

(シングルトン)オブジェクト object AbsentNode: Node { override val value: Nothing get() = throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" } プロパティのオーバライド 内部フィールドを持たない。カスタムgetterを提供

Slide 58

Slide 58 text

リストを作れる! PresentNode(1, PresentNode(2, AbsentNode)) //=> 1 -> 2 -> X PresentNode(1.2, PresentNode(3, AbsentNode)) //=> 1.2 -> 3 -> X

Slide 59

Slide 59 text

リストを作れる! PresentNode(1, PresentNode(2, AbsentNode)) //=> 1 -> 2 -> X PresentNode(1.2, PresentNode(3, AbsentNode)) //=> 1.2 -> 3 -> X

Slide 60

Slide 60 text

リストを作れる! PresentNode(1, PresentNode(2, AbsentNode)) //=> 1 -> 2 -> X PresentNode(1.2, PresentNode(3, AbsentNode)) //=> 1.2 -> 3 -> X class Node ↑クラス定義時点における共変指定

Slide 61

Slide 61 text

コンパニオンオブジェクト abstract class Node { companion object { val nil: Node = AbsentNode fun of(value: T, next: Node = nil) = PresentNode(value, next) } ... }

Slide 62

Slide 62 text

コンパニオンオブジェクト abstract class Node { companion object { val nil: Node = AbsentNode fun of(value: T, next: Node = nil) = PresentNode(value, next) } ... } コンパニオンオブジェクト

Slide 63

Slide 63 text

コンパニオンオブジェクトのメンバを使う Node.of(1, Node.of(2, Node.nil)) //=> 1 -> 2 -> X Node.of(1.2, Node.of(3, Node.of(4)) //=> 1.2 -> 3 -> 4 -> X

Slide 64

Slide 64 text

コンパニオンオブジェクトのメンバを使う Node.of(1, Node.of(2, Node.nil)) //=> 1 -> 2 -> X Node.of(1.2, Node.of(3, Node.of(4)) //=> 1.2 -> 3 -> 4 -> X

Slide 65

Slide 65 text

デフォルト引数 abstract class Node { companion object { val nil: Node = AbsentNode fun of(value: T, next: Node = nil) = PresentNode(value, next) } ... } 呼び出し時に引数を省略すると デフォルト値が使用される

Slide 66

Slide 66 text

通常の抽象クラスは見える人なら継承OK abstract class Node {...} class PresentNode(...): Node() {...} object AbsentNode: Node() {...} class MyNode: Node() {...}

Slide 67

Slide 67 text

通常の抽象クラスは見える人なら継承OK abstract class Node {...} class PresentNode(...): Node() {...} object AbsentNode: Node() {...} class MyNode: Node() {...} 今回つくったやつ

Slide 68

Slide 68 text

abstract class Node {...} class PresentNode(...): Node() {...} object AbsentNode: Node() {...} class MyNode: Node() {...} 通常の抽象クラスは見える人なら継承OK 第3者が継承させることができる。でも都合が悪い

Slide 69

Slide 69 text

シールドクラスで継承を制限 sealed class Node { ... class PresentNode(...): Node() {...} object AbsentNode: Node() {...} } class MyNode: Node() {...}

Slide 70

Slide 70 text

シールドクラスで継承を制限 sealed class Node { ... class PresentNode(...): Node() {...} object AbsentNode: Node() {...} } class MyNode: Node() {...} ↓この人はもはや継承できない(コンパイルエラー) 注: ver1.1では、シールドクラスの継承可能な範囲が その内部クラスから同一ファイル内へと緩和される。

Slide 71

Slide 71 text

ノード数プロパティ size sealed class Node { ... val size: Int get() { tailrec fun go(node: Node, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode -> go(node.next, acc + 1) } return go(this, 0) } }

Slide 72

Slide 72 text

ノード数プロパティ size sealed class Node { ... val size: Int get() { tailrec fun go(node: Node, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode -> go(node.next, acc + 1) } return go(this, 0) } } プロパティ size 内部フィールドを持たず、カスタム getterを提供

Slide 73

Slide 73 text

ノード数プロパティ size sealed class Node { ... val size: Int get() { tailrec fun go(node: Node, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode -> go(node.next, acc + 1) } return go(this, 0) } } 再帰呼び出しでノード数を計算

Slide 74

Slide 74 text

ノード数プロパティ size sealed class Node { ... val size: Int get() { tailrec fun go(node: Node, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode -> go(node.next, acc + 1) } return go(this, 0) } } when式=switchの強い版 分岐の網羅が必須

Slide 75

Slide 75 text

委譲プロパティによる遅延初期化 sealed class Node { ... val size: Int by lazy { tailrec fun go(node: Node, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode -> go(node.next, acc + 1) } go(this, 0) } }

Slide 76

Slide 76 text

委譲プロパティによる遅延初期化 sealed class Node { ... val size: Int by lazy { tailrec fun go(node: Node, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode -> go(node.next, acc + 1) } go(this, 0) } } プロパティアクセスを byの後に続くオブジェクトに委譲

Slide 77

Slide 77 text

委譲プロパティによる遅延初期化 sealed class Node { ... val size: Int by lazy { tailrec fun go(node: Node, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode -> go(node.next, acc + 1) } go(this, 0) } } 標準関数 lazy 委譲される遅延初期化用オブジェクト を生成する

Slide 78

Slide 78 text

委譲プロパティによる遅延初期化 sealed class Node { ... val size: Int by lazy { tailrec fun go(node: Node, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode -> go(node.next, acc + 1) } go(this, 0) } } lazyの引数としてのラムダ式

Slide 79

Slide 79 text

本日登場したキーワード ● クラス ● プロパティ ● メソッド ● プライマリコンストラクタ ● オーバライド ● String Templates ● イニシャライザ ● if-else ● TCO ● 演算子オーバロード ● オーバロード ● 拡張関数 ● 抽象クラス ● 抽象プロパティ ● 共変 ● 継承 ● オブジェクト ● Nothing型 ● コンパニオンオブジェクト ● デフォルト引数 ● シールドクラス ● when式 ● 委譲プロパティ ● ラムダ式

Slide 80

Slide 80 text

あなたと赤べこ、今すぐ書店へ