Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Taro Nagasawa
February 20, 2017

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

JJUGナイトセミナー( https://jjug.doorkeeper.jp/events/57443 )で発表した資料です。

Taro Nagasawa

February 20, 2017
Tweet

More Decks by Taro Nagasawa

Other Decks in Programming

Transcript

  1. エバンジェリストな私 • Kotlin歴 5年 • 日本Kotlinユーザグループ代表 • 講演実績多数 ◦ DroidKaigi

    2015, 2016, 2017予定 ◦ JJUG CCC 2015 Fall ◦ 福岡、京都など遠征も • 執筆実績多数 ◦ 単行本、商業誌、同人誌
  2. • 有理数クラス Rational • プロパティ ◦ 分子(numerator) ◦ 分母(denominator) •

    メソッド ◦ いろんな計算を提供 題材その1: 有理数を表現するクラス
  3. クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方

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

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

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

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

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

    fun main(args: Array<String>) { val half = Rational(1, 2) println(half.denominator) // 2 } プロパティにアクセス: Javaで言うフィールドとアクセサが一緒になったようなもの
  9. ちなみに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; } }
  10. メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override

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

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

    fun toString(): String = "${numerator}/${denominator}" } 式が評価された文字列内に展開される = String Templates
  13. 確認しやすくなった! fun main(args: Array<String>) { println(Rational(2, 5)) // 「2/5」 println(Rational(3,

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

    0)) // 「3/0」 println(Rational(4, 10)) // 「4/10」 println(Rational(9, 3)) // 「9/3」 } 分母ゼロを禁止したい!
  15. イニシャライザ class Rational(val numerator: Int, val denominator: Int) { init

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

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

    { require(denominator != 0, {"ゼロはダメ"}) } override fun toString(): String = ... } 標準関数
  18. もう大丈夫!? fun main(args: Array<String>) { println(Rational(2, 5)) // 「2/5」 println(Rational(3,

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

    0)) // 例外 println(Rational(4, 10)) // 「4/10」 println(Rational(9, 3)) // 「9/3」 } 約分したい!
  20. 非公開メソッド 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の最大公約数を返すメソッド
  21. 非公開メソッド 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は式(値を返す)
  22. 非公開メソッド 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) } 末尾再帰なので、最適化可能
  23. 非公開プロパティ 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 ... }
  24. 非公開プロパティ 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 ... } コンストラクタ引数(プロパティでない)
  25. 非公開プロパティ 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 ... } 非公開プロパティ
  26. 非公開プロパティ 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 ... } 公開プロパティ
  27. すばらしい! fun main(args: Array<String>) { println(Rational(2, 5)) // 「2/5」 println(Rational(3,

    0)) // 例外 println(Rational(4, 10)) // 「2/5」 println(Rational(9, 3)) // 「3/1」 }
  28. 演算子オーバロード class Rational(n: Int, d: Int) { ... operator fun

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

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

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

    plus(r: Rational): Rational = ... operator fun plus(i: Int): Rational = Rational(numerator + i * denominator, denominator) }
  32. 拡張関数 class Rational(n: Int, d: Int) { ... } operator

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

    fun Int.times(r: Rational): Rational = Rational(r.numerator * this, r.denominator) Intの拡張関数(しかも演算子オーバロード)
  34. • 片方向リストのノード 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)))
  35. abstract class Node<out T> { abstract val value: T abstract

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

    val next: Node<T> } 抽象クラス 共変指定 Java的には <? extends T>
  37. 継承してノードを表現 class PresentNode<out T> (override val value: T, override val

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

    next: Node<T>): Node<T>() { override fun toString(): String = "${value} -> ${next}" } 継承 スーパクラスのコンストラクタ呼び出し
  39. class PresentNode<out T> (override val value: T, override val next:

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

    next: Node<T>): Node<T>() { override fun toString(): String = "${value} -> ${next}" } ついでに 「1 -> 2 -> 3 -> X」のような表現を
  41. (シングルトン)オブジェクト object AbsentNode: Node<Nothing> { override val value: Nothing get()

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

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

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

    = throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" } プロパティのオーバライド 内部フィールドを持たない。カスタムgetterを提供
  45. リストを作れる! PresentNode(1, PresentNode(2, AbsentNode)) //=> 1 -> 2 -> X

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

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

    PresentNode<Number>(1.2, PresentNode<Int>(3, AbsentNode)) //=> 1.2 -> 3 -> X class Node<out T> ↑クラス定義時点における共変指定
  48. コンパニオンオブジェクト abstract class Node<out T> { companion object { val

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

    nil: Node<Nothing> = AbsentNode fun <T> of(value: T, next: Node<T> = nil) = PresentNode(value, next) } ... } コンパニオンオブジェクト
  50. デフォルト引数 abstract class Node<out T> { companion object { val

    nil: Node<Nothing> = AbsentNode fun <T> of(value: T, next: Node<T> = nil) = PresentNode(value, next) } ... } 呼び出し時に引数を省略すると デフォルト値が使用される
  51. 通常の抽象クラスは見える人なら継承OK abstract class Node<out T> {...} class PresentNode<out T>(...): Node<T>()

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

    {...} object AbsentNode: Node<Nothiing>() {...} class MyNode<out T>: Node<T>() {...} 今回つくったやつ
  53. abstract class Node<out T> {...} class PresentNode<out T>(...): Node<T>() {...}

    object AbsentNode: Node<Nothiing>() {...} class MyNode<out T>: Node<T>() {...} 通常の抽象クラスは見える人なら継承OK 第3者が継承させることができる。でも都合が悪い
  54. シールドクラスで継承を制限 sealed class Node<out T> { ... class PresentNode<out T>(...):

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

    Node<T>() {...} object AbsentNode: Node<Nothing>() {...} } class MyNode<out T>: Node<T>() {...} ↓この人はもはや継承できない(コンパイルエラー) 注: ver1.1では、シールドクラスの継承可能な範囲が その内部クラスから同一ファイル内へと緩和される。
  56. ノード数プロパティ size sealed class Node<out T> { ... val size:

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

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

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

    Int get() { tailrec fun go(node: Node<T>, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode<T> -> go(node.next, acc + 1) } return go(this, 0) } } when式=switchの強い版 分岐の網羅が必須
  60. 委譲プロパティによる遅延初期化 sealed class Node<out T> { ... val size: Int

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

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

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

    by lazy { tailrec fun go(node: Node<T>, acc: Int): Int = when(node) { is AbsentNode -> acc is PresentNode<T> -> go(node.next, acc + 1) } go(this, 0) } } lazyの引数としてのラムダ式
  64. 本日登場したキーワード • クラス • プロパティ • メソッド • プライマリコンストラクタ •

    オーバライド • String Templates • イニシャライザ • if-else • TCO • 演算子オーバロード • オーバロード • 拡張関数 • 抽象クラス • 抽象プロパティ • 共変 • 継承 • オブジェクト • Nothing型 • コンパニオンオブジェクト • デフォルト引数 • シールドクラス • when式 • 委譲プロパティ • ラムダ式