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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Taro Nagasawa
February 20, 2017
Programming
4
1.7k
クラスの作り方に見る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
1.3k
Kotlin 最新動向2022 #tfcon #techfeed
ntaro
1
2.3k
#Ubie 狂気の認知施策と選考設計
ntaro
13
14k
UbieにおけるサーバサイドKotlin活用事例
ntaro
1
1.2k
KotlinでSpring 完全理解ガイド #jsug
ntaro
6
3.6k
Kotlinでサーバサイドを始めよう!
ntaro
1
1k
Androidからサーバーサイドまで!プログラミング言語 Kotlinの魅力 #devboost
ntaro
5
2.9k
Kotlin Contracts #m3kt
ntaro
4
4.3k
How_to_Test_Server-side_Kotlin.pdf
ntaro
1
540
Other Decks in Programming
See All in Programming
Symfony + NelmioApiDocBundle を使った スキーマ駆動開発 / Schema Driven Development with NelmioApiDocBundle
okashoi
0
170
RAGでハマりがちな"Excelの罠"を、データの構造化で突破する
harumiweb
9
2.9k
CSC307 Lecture 15
javiergs
PRO
0
260
LangChain4jとは一味違うLangChain4j-CDI
kazumura
1
200
AWS Infrastructure as Code の新機能 2025 総まとめ 〜SA 4人による怒涛のデモ祭り〜
konokenj
10
3.4k
ふつうの Rubyist、ちいさなデバイス、大きな一年
bash0c7
0
1.1k
生成 AI 時代のスナップショットテストってやつを見せてあげますよ(α版)
ojun9
0
260
AHC061解説
shun_pi
0
400
Redox OS でのネームスペース管理と chroot の実現
isanethen
0
250
車輪の再発明をしよう!PHP で実装して学ぶ、Web サーバーの仕組みと HTTP の正体
h1r0
0
130
AI 開発合宿を通して得た学び
niftycorp
PRO
0
150
ベクトル検索のフィルタを用いた機械学習モデルとの統合 / python-meetup-fukuoka-06-vector-attr
monochromegane
2
470
Featured
See All Featured
How to train your dragon (web standard)
notwaldorf
97
6.6k
Fireside Chat
paigeccino
42
3.8k
Making the Leap to Tech Lead
cromwellryan
135
9.8k
The Language of Interfaces
destraynor
162
26k
Ruling the World: When Life Gets Gamed
codingconduct
0
180
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
140
Crafting Experiences
bethany
1
89
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.1k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.1k
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
130
ラッコキーワード サービス紹介資料
rakko
1
2.7M
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
1
160
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式 • 委譲プロパティ • ラムダ式
あなたと赤べこ、今すぐ書店へ