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.4k
クラスの作り方に見る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
Kotlin 最新動向2022 #tfcon #techfeed
ntaro
1
2k
#Ubie 狂気の認知施策と選考設計
ntaro
13
12k
UbieにおけるサーバサイドKotlin活用事例
ntaro
1
1k
KotlinでSpring 完全理解ガイド #jsug
ntaro
6
3.1k
Kotlinでサーバサイドを始めよう!
ntaro
1
840
Androidからサーバーサイドまで!プログラミング言語 Kotlinの魅力 #devboost
ntaro
5
2.3k
Kotlin Contracts #m3kt
ntaro
4
3.4k
How_to_Test_Server-side_Kotlin.pdf
ntaro
1
380
Kotlin Fest 2018 - Opening session
ntaro
0
4.2k
Other Decks in Programming
See All in Programming
Learning PHP and Static Analysis with PHP Parser
inouehi
1
250
導入から5年が経って見えた Datadog APM 運用の課題
bgpat
2
540
Honoとhtmx
yusukebe
6
1.2k
phpunit/php-code-coverageって何をしてるんだ #phperkaigi
o0h
PRO
2
210
Compiling Python to WebAssembly with py2wasm
syrusakbary
0
130
孤独のCTOグルメという やや奇抜な企画をやった目的と効果
shoheimitani
3
1k
ISUCONってなんだか難しそう……!!でも、初めてのISUCONにPHPで挑戦してきました!
kotomin_m
1
300
自作ソフト(VMagicMirror)がVRMA対応してる話+実装のTips
bakudreameater
0
110
TDDと今まで
kanayannet
0
140
CSRF対策のやり方、そろそろアップデートしませんか / Update your knowledge of CSRF protection
hiro_y
25
14k
Cloud RunとCloud PubSubでサーバレスなデータ基盤2024 with Terraform / Cloud Run and PubSub with Terraform
shinyorke
7
1.9k
Parallel Socket Communication in Swift
s_shimotori
0
210
Featured
See All Featured
A Tale of Four Properties
chriscoyier
150
22k
Java REST API Framework Comparison - PWX 2021
mraible
PRO
18
6.8k
The Illustrated Children's Guide to Kubernetes
chrisshort
28
46k
The Pragmatic Product Professional
lauravandoore
24
5.7k
Making the Leap to Tech Lead
cromwellryan
122
8.4k
How to name files
jennybc
62
92k
Large-scale JavaScript Application Architecture
addyosmani
501
110k
Code Reviewing Like a Champion
maltzj
512
39k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
6
950
Clear Off the Table
cherdarchuk
82
310k
Building Better People: How to give real-time feedback that sticks.
wjessup
350
18k
What’s in a name? Adding method to the madness
productmarketing
PRO
14
2.6k
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式 • 委譲プロパティ • ラムダ式
あなたと赤べこ、今すぐ書店へ