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.6k
クラスの作り方に見る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
780
Kotlin 最新動向2022 #tfcon #techfeed
ntaro
1
2.3k
#Ubie 狂気の認知施策と選考設計
ntaro
13
13k
UbieにおけるサーバサイドKotlin活用事例
ntaro
1
1.1k
KotlinでSpring 完全理解ガイド #jsug
ntaro
6
3.5k
Kotlinでサーバサイドを始めよう!
ntaro
1
1k
Androidからサーバーサイドまで!プログラミング言語 Kotlinの魅力 #devboost
ntaro
5
2.8k
Kotlin Contracts #m3kt
ntaro
4
4.2k
How_to_Test_Server-side_Kotlin.pdf
ntaro
1
520
Other Decks in Programming
See All in Programming
開発生産性を上げるための生成AI活用術
starfish719
3
350
Flutterで分数(Fraction)を表示する方法
koukimiura
0
120
CSC305 Lecture 04
javiergs
PRO
0
260
オープンソースソフトウェアへの解像度🔬
utam0k
11
2.2k
Railsだからできる 例外業務に禍根を残さない 設定設計パターン
ei_ei_eiichi
0
410
株式会社 Sun terras カンパニーデック
sunterras
0
260
GitHub Actions × AWS OIDC連携の仕組みと経緯を理解する
ota1022
0
250
Cloudflare AgentsとAI SDKでAIエージェントを作ってみた
briete
0
130
技術的負債の正体を知って向き合う / Facing Technical Debt
irof
0
130
Serena MCPのすすめ
wadakatu
4
950
CSC509 Lecture 03
javiergs
PRO
0
330
ソフトウェア設計の実践的な考え方
masuda220
PRO
4
530
Featured
See All Featured
[RailsConf 2023] Rails as a piece of cake
palkan
57
5.9k
Faster Mobile Websites
deanohume
310
31k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
Context Engineering - Making Every Token Count
addyosmani
5
210
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
15k
Optimizing for Happiness
mojombo
379
70k
Why You Should Never Use an ORM
jnunemaker
PRO
59
9.6k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
3.1k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
114
20k
Rebuilding a faster, lazier Slack
samanthasiow
84
9.2k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
7
900
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式 • 委譲プロパティ • ラムダ式
あなたと赤べこ、今すぐ書店へ