Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
クラスの作り方に見るKotlinの表現力
Search
Taro Nagasawa
February 20, 2017
Programming
4
1.5k
クラスの作り方に見るKotlinの表現力
JJUGナイトセミナー(
https://jjug.doorkeeper.jp/events/57443
)で発表した資料です。
Taro Nagasawa
February 20, 2017
Tweet
Share
More Decks by Taro Nagasawa
See All by Taro Nagasawa
Android開発者のための Kotlin Multiplatform入門
ntaro
0
490
Kotlin 最新動向2022 #tfcon #techfeed
ntaro
1
2.2k
#Ubie 狂気の認知施策と選考設計
ntaro
13
13k
UbieにおけるサーバサイドKotlin活用事例
ntaro
1
1.1k
KotlinでSpring 完全理解ガイド #jsug
ntaro
6
3.4k
Kotlinでサーバサイドを始めよう!
ntaro
1
940
Androidからサーバーサイドまで!プログラミング言語 Kotlinの魅力 #devboost
ntaro
5
2.6k
Kotlin Contracts #m3kt
ntaro
4
3.9k
How_to_Test_Server-side_Kotlin.pdf
ntaro
1
450
Other Decks in Programming
See All in Programming
traP の部内 ISUCON とそれを支えるポータル / PISCON Portal
ikura_hamu
0
180
Jaspr Dart Web Framework 박제창 @Devfest 2024
itsmedreamwalker
0
150
Scaling your build logic
antalmonori
1
100
PSR-15 はあなたのための ものではない? - phpcon2024
myamagishi
0
410
ChatGPT とつくる PHP で OS 実装
memory1994
PRO
3
190
責務を分離するための例外設計 - PHPカンファレンス 2024
kajitack
9
2.4k
AWS re:Invent 2024個人的まとめ
satoshi256kbyte
0
100
Simple組み合わせ村から大都会Railsにやってきた俺は / Coming to Rails from the Simple
moznion
3
2.2k
Package Traits
ikesyo
1
210
各クラウドサービスにおける.NETの対応と見解
ymd65536
0
250
20年もののレガシープロダクトに 0からPHPStanを入れるまで / phpcon2024
hirobe1999
0
1k
AHC041解説
terryu16
0
400
Featured
See All Featured
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
45
2.3k
Typedesign – Prime Four
hannesfritz
40
2.5k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
132
33k
Rails Girls Zürich Keynote
gr2m
94
13k
The Straight Up "How To Draw Better" Workshop
denniskardys
232
140k
Embracing the Ebb and Flow
colly
84
4.5k
Unsuck your backbone
ammeep
669
57k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
3.6k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
3
360
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
160
15k
Done Done
chrislema
182
16k
Transcript
クラスの作り方に見る Kotlinの表現力 2017-02-20 JJUGナイトセミナー 長澤 太郎 @ngsw_taro
Kotlinとは? • Java仮想マシンをターゲットとしたプログラミング言語 ◦ JavaScriptやAndroidもサポート • IntelliJ IDEAでおなじみのJetBrainsが開発 • 2011年に発表され、2016年2月にver1.0がリリース
• 現在 ver1.0.6, 1.1-beta2 • 静的型付けオブジェクト指向言語 • 簡潔、安全、Javaとの相互運用性
Hello World package sample fun main(args: Array<String>) { if(args.isEmpty()) return
val name = args[0] println("Hello, ${name}!") }
クラスの作り方に見る Kotlinの表現力
• 長澤 太郎 たろーって呼んでね • @ngsw_taro 自己紹介
エムスリー株式会社 jobs.m3.com/engineer
エバンジェリストな私 • Kotlin歴 5年 • 日本Kotlinユーザグループ代表 • 講演実績多数 ◦ DroidKaigi
2015, 2016, 2017予定 ◦ JJUG CCC 2015 Fall ◦ 福岡、京都など遠征も • 執筆実績多数 ◦ 単行本、商業誌、同人誌
もくじ 1. 有理数 2. 片方向リストのノード
• 有理数クラス Rational • プロパティ ◦ 分子(numerator) ◦ 分母(denominator) •
メソッド ◦ いろんな計算を提供 題材その1: 有理数を表現するクラス
クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方
fun main(args: Array<String>) { val half = Rational(1, 2) println(half.denominator) // 2 }
クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方
fun main(args: Array<String>) { val half = Rational(1, 2) println(half.denominator) // 2 } クラス名
クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方
fun main(args: Array<String>) { val half = Rational(1, 2) println(half.denominator) // 2 } プライマリコンストラクタ
クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方
fun main(args: Array<String>) { val half = Rational(1, 2) println(half.denominator) // 2 } プロパティ
クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方
fun main(args: Array<String>) { val half = Rational(1, 2) println(half.denominator) // 2 } インスタンス生成
クラス定義 class Rational(val numerator: Int, val denominator: Int) // 使い方
fun main(args: Array<String>) { val half = Rational(1, 2) println(half.denominator) // 2 } プロパティにアクセス: Javaで言うフィールドとアクセサが一緒になったようなもの
ちなみに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; } }
メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override
fun toString() = "${numerator}/${denominator}" }
メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override
fun toString(): String = "${numerator}/${denominator}" } オーバライドに必須
メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override
fun toString(): String = "${numerator}/${denominator}" } イコールで式を結びつける
メソッドtoStringをオーバライド class Rational(val numerator: Int, val denominator: Int) { override
fun toString(): String = "${numerator}/${denominator}" } 式が評価された文字列内に展開される = String Templates
確認しやすくなった! 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」 }
確認しやすくなった! 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」 } 分母ゼロを禁止したい!
イニシャライザ class Rational(val numerator: Int, val denominator: Int) { init
{ if(denominator == 0) throw IllegalArgumentException("ゼロはダメ") } override fun toString(): String = ... }
イニシャライザ class Rational(val numerator: Int, val denominator: Int) { init
{ if(denominator == 0) throw IllegalArgumentException("ゼロはダメ") } override fun toString(): String = ... } イニシャライザ
イニシャライザ class Rational(val numerator: Int, val denominator: Int) { init
{ require(denominator != 0, {"ゼロはダメ"}) } override fun toString(): String = ... } 標準関数
もう大丈夫!? 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」 }
もう大丈夫!? 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」 } 約分したい!
非公開メソッド 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の最大公約数を返すメソッド
非公開メソッド 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は式(値を返す)
非公開メソッド 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) } 末尾再帰なので、最適化可能
非公開プロパティ 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 ... }
非公開プロパティ 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 ... } コンストラクタ引数(プロパティでない)
非公開プロパティ 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 ... } 非公開プロパティ
非公開プロパティ 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 ... } 公開プロパティ
すばらしい! 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」 }
演算子オーバロード class Rational(n: Int, d: Int) { ... operator fun
plus(r: Rational): Rational = Rational( numerator * r.denominator + r.numerator + denominator, denominator * r.denominator ) }
演算子オーバロード class Rational(n: Int, d: Int) { ... operator fun
plus(r: Rational): Rational = Rational( numerator * r.denominator + r.numerator + denominator, denominator * r.denominator ) } 有理数との足し算メソッド
演算子オーバロード class Rational(n: Int, d: Int) { ... operator fun
plus(r: Rational): Rational = Rational( numerator * r.denominator + r.numerator + denominator, denominator * r.denominator ) } +演算子を使ったメソッド呼び出しが可能に
演算子で計算できる! Rational(1, 6) + Rational(1, 3) //=> 1/2
オーバロード class Rational(n: Int, d: Int) { ... operator fun
plus(r: Rational): Rational = ... operator fun plus(i: Int): Rational = Rational(numerator + i * denominator, denominator) }
演算子で計算できる! Rational(1, 6) + Rational(1, 3) //=> 1/2 Rational(1, 4)
+ 2 //=> 9/4
じゃあこれは? Rational(1, 6) + Rational(1, 3) //=> 1/2 Rational(1, 4)
+ 2 //=> 9/4 2 * Rational(2, 5) // ???
拡張関数 class Rational(n: Int, d: Int) { ... } operator
fun Int.times(r: Rational): Rational = Rational(r.numerator * this, r.denominator)
拡張関数 class Rational(n: Int, d: Int) { ... } operator
fun Int.times(r: Rational): Rational = Rational(r.numerator * this, r.denominator) Intの拡張関数(しかも演算子オーバロード)
できた!! Rational(1, 6) + Rational(1, 3) //=> 1/2 Rational(1, 4)
+ 2 //=> 9/4 2 * Rational(2, 5) // 4/5
• 片方向リストのノード 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)))
抽象クラス abstract class Node<out T> { abstract val value: T
abstract val next: Node<T> }
abstract class Node<out T> { abstract val value: T abstract
val next: Node<T> } 抽象クラス 抽象プロパティ
abstract class Node<out T> { abstract val value: T abstract
val next: Node<T> } 抽象クラス 共変指定 Java的には <? extends T>
継承してノードを表現 class PresentNode<out T> (override val value: T, override val
next: Node<T>): Node<T>() { override fun toString(): String = "${value} -> ${next}" }
継承してノードを表現 class PresentNode<out T> (override val value: T, override val
next: Node<T>): Node<T>() { override fun toString(): String = "${value} -> ${next}" } 継承 スーパクラスのコンストラクタ呼び出し
class PresentNode<out T> (override val value: T, override val next:
Node<T>): Node<T>() { override fun toString(): String = "${value} -> ${next}" } 継承してノードを表現 プロパティのオーバライド 内部フィールド + そのアクセサを自動提供
継承してノードを表現 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」のような表現を
(シングルトン)オブジェクト object AbsentNode: Node<Nothing> { override val value: Nothing get()
= throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" }
(シングルトン)オブジェクト object AbsentNode: Node<Nothing> { override val value: Nothing get()
= throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" }
(シングルトン)オブジェクト object AbsentNode: Node<Nothing> { override val value: Nothing get()
= throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" } あらゆる型のサブタイプ
(シングルトン)オブジェクト object AbsentNode: Node<Nothing> { override val value: Nothing get()
= throw UnsupportedOperationException() override val next: Nothing get() = throw UnsupportedOperationException() override fun toString(): String = "X" } プロパティのオーバライド 内部フィールドを持たない。カスタムgetterを提供
リストを作れる! PresentNode(1, PresentNode(2, AbsentNode)) //=> 1 -> 2 -> X
PresentNode<Number>(1.2, PresentNode<Int>(3, AbsentNode)) //=> 1.2 -> 3 -> X
リストを作れる! PresentNode(1, PresentNode(2, AbsentNode)) //=> 1 -> 2 -> X
PresentNode<Number>(1.2, PresentNode<Int>(3, AbsentNode)) //=> 1.2 -> 3 -> X
リストを作れる! PresentNode(1, PresentNode(2, AbsentNode)) //=> 1 -> 2 -> X
PresentNode<Number>(1.2, PresentNode<Int>(3, AbsentNode)) //=> 1.2 -> 3 -> X class Node<out T> ↑クラス定義時点における共変指定
コンパニオンオブジェクト abstract class Node<out T> { companion object { val
nil: Node<Nothing> = AbsentNode fun <T> of(value: T, next: Node<T> = nil) = PresentNode(value, next) } ... }
コンパニオンオブジェクト abstract class Node<out T> { companion object { val
nil: Node<Nothing> = AbsentNode fun <T> of(value: T, next: Node<T> = nil) = PresentNode(value, next) } ... } コンパニオンオブジェクト
コンパニオンオブジェクトのメンバを使う 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
コンパニオンオブジェクトのメンバを使う 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
デフォルト引数 abstract class Node<out T> { companion object { val
nil: Node<Nothing> = AbsentNode fun <T> of(value: T, next: Node<T> = nil) = PresentNode(value, next) } ... } 呼び出し時に引数を省略すると デフォルト値が使用される
通常の抽象クラスは見える人なら継承OK abstract class Node<out T> {...} class PresentNode<out T>(...): Node<T>()
{...} object AbsentNode: Node<Nothiing>() {...} class MyNode<out T>: Node<T>() {...}
通常の抽象クラスは見える人なら継承OK abstract class Node<out T> {...} class PresentNode<out T>(...): Node<T>()
{...} object AbsentNode: Node<Nothiing>() {...} class MyNode<out T>: Node<T>() {...} 今回つくったやつ
abstract class Node<out T> {...} class PresentNode<out T>(...): Node<T>() {...}
object AbsentNode: Node<Nothiing>() {...} class MyNode<out T>: Node<T>() {...} 通常の抽象クラスは見える人なら継承OK 第3者が継承させることができる。でも都合が悪い
シールドクラスで継承を制限 sealed class Node<out T> { ... class PresentNode<out T>(...):
Node<T>() {...} object AbsentNode: Node<Nothing>() {...} } class MyNode<out T>: Node<T>() {...}
シールドクラスで継承を制限 sealed class Node<out T> { ... class PresentNode<out T>(...):
Node<T>() {...} object AbsentNode: Node<Nothing>() {...} } class MyNode<out T>: Node<T>() {...} ↓この人はもはや継承できない(コンパイルエラー) 注: ver1.1では、シールドクラスの継承可能な範囲が その内部クラスから同一ファイル内へと緩和される。
ノード数プロパティ 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 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を提供
ノード数プロパティ 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 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の強い版 分岐の網羅が必須
委譲プロパティによる遅延初期化 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) } }
委譲プロパティによる遅延初期化 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の後に続くオブジェクトに委譲
委譲プロパティによる遅延初期化 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 委譲される遅延初期化用オブジェクト を生成する
委譲プロパティによる遅延初期化 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の引数としてのラムダ式
本日登場したキーワード • クラス • プロパティ • メソッド • プライマリコンストラクタ •
オーバライド • String Templates • イニシャライザ • if-else • TCO • 演算子オーバロード • オーバロード • 拡張関数 • 抽象クラス • 抽象プロパティ • 共変 • 継承 • オブジェクト • Nothing型 • コンパニオンオブジェクト • デフォルト引数 • シールドクラス • when式 • 委譲プロパティ • ラムダ式
あなたと赤べこ、今すぐ書店へ