1. 背景AndroidアプリでKotlinを使用したので備忘録 Kotlinの概念がかなりAdvancedなのでまとめる NOTE:
2. 基本2.1. 変数・定数1
2
var mutable = 20
val immutable = 10
Copy 2.2. const
とval
の違いconstは主にコンパイル時に値が確定している定数を定義する場合に使用し、valは変更不可能な変数を定義する際に広く使用する。
特性 const val 宣言のタイミング コンパイル時に値が決定 実行時に値が決定 使用可能な場所 トップレベルまたはobjectの中のみ どこでも使用可能(クラス内、関数内、トップレベルなど) 使用可能な型 プリミティブ型とString型のみ すべての型 初期化 宣言時に初期化必須 宣言後に初期化可能(ただし一度だけ) パフォーマンス コンパイル時決定のため若干向上 実行時に値が決定 参照の扱い 値そのものがインライン展開 参照として扱われる
1
2
3
4
5
// const の例
const val MAX_COUNT = 100
object Constants {
const val API_KEY = "abcdef123456"
}
Copy 2.3. パターンマッチングwhen
式を使用して、関数型言語のパターンマッチングが可能。
1
2
3
4
5
when ( x ) {
is Int -> println ( "It's an Int" )
is String -> println ( "It's a String" )
else -> println ( "Unknown type" )
}
Copy 2.4. コレクション1
2
val list = listOf ( 1 , 2 , 3 )
val map = mapOf ( "a" to 1 , "b" to 2 )
Copy 2.5. Range 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//1~10の範囲
for ( i in 1. . 10 ){
print ( i ) //「12345678910」
}
//2~10の範囲で2刻み
for ( i in 2. . 10 step 2 ){
print ( i ) //「246810」
}
//9~1の範囲(逆順)で2刻み
for ( i in 9 downTo 1 step 2 ){
print ( i ) //「97531」
}
println ()
//1~10の範囲。ただし終わりの値(10)は含まない
for ( i in 1 until 10 ){
print ( i ) //「123456789」
}
println ()
//a~zの範囲
for ( i in 'a' .. 'z' ){
print ( i ) // 「abcdefghijklmnopqrstuvwxyz」
}
//A~zの範囲で2刻み
for ( i in 'A' until 'z' step 2 ){
print ( i ) // 「ACEGIKMOQSUWY[]_acegikmoqsuwy」
}
Copy 2.6. 分解宣言と多重戻り値 1
2
3
4
5
6
7
8
9
10
11
12
13
fun addAndMul ( a : Int , b : Int ): Pair < Int , Int >{
return Pair ( a + b , a * b )
}
val ( wa , seki ) = addAndMul ( 2 , 5 )
println ( "和: $wa , 積: $seki " ) //「和: 7, 積: 10」
val result = addAndMul ( 2 , 5 )
//分解宣言は内部的にcomponentN関数を呼び出している
val wa2 = result . component1 ()
val seki2 = result . component2 ()
println ( "和: $wa2 , 積: $seki2 " ) //「和: 7, 積: 10」
Copy data class
1
2
3
4
5
6
7
8
//データクラスは自動でcomponentNというメソッドが定義されているので分解宣言がそのまま使える
data class Person ( val firstName : String , val lastName : String , val age : Int )
……
//データクラスのインスタンスを作る
val tanaka = Person ( "一郎" , "田中" , 25 )
//データクラスのオブジェクトを複数の変数に分解して代入する
val ( mei , sei , age ) = tanaka
println ( "姓: $sei , 名: $mei , 年齢: $age " ) //「姓: 田中, 名: 一郎, 年齢: 25」
Copy 3. 型3.1. 代数的データ型代数的データ型には主に2種類ある。
直積型(Product Types)複数の値を同時に持つ型 Kotlinでは、data classがこれに相当 直和型(Sum Types)複数の型の中から1つを選択する型 Kotlinでは、sealedクラスがこれに相当 直積型(Product Types)の例は次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person (
val name : String ,
val age : Int ,
val email : String
)
// 使用例
val john = Person ( "John Doe" , 30 , "john@example.com" )
// デストラクチャリング
val ( name , age , email ) = john
println ( "Name: $name , Age: $age , Email: $email " )
// コピーと修正
val olderJohn = john . copy ( age = 31 )
Copy 直和型の例
1
2
3
4
5
sealed class ApiResult < out T > {
data class Success < out T >( val data : T ) : ApiResult < T >()
data class Error ( val message : String ) : ApiResult < Nothing >()
object Loading : ApiResult < Nothing >()
}
Copy このApiResultは、「成功」「エラー」「読み込み中」という3つの異なる状態のいずれかを必ず取る シンプルに直和型を表現したいときは、enumでもできる。
1
2
3
4
5
enum class ApiResult {
SUCCESS ,
ERROR ,
LOADING
}
Copy 3.2. 型エイリアスtypealias
を用いて型に別名をつけることができる。
1
2
typealias Meter = Int
typealias Killometer = Int
Copy 3.3. 型の確認(is
)is
演算子で型チェックができる。
1
2
3
4
5
6
7
8
9
10
11
println ( "ABC" is String ) // true
println ( "ABC" !is String ) // false
open class ClassA () {}
class ClassB (): ClassA () {}
var a = ClassA ()
var b = ClassB ()
println ( a is ClassA ) // true
println ( a is ClassB ) // false
println ( b is ClassA ) // true
println ( b is ClassB ) // true
Copy 3.4. 型のキャスト(as
, as?
)as
を用いて、スーパークラスをサブクラスにキャストできる。
1
2
3
open class Parent () {}
class Child (): Parent () {}
var c : Child = Parent () as Child
Copy キャストできない場合は例外が発生するが、as?
を用いるとキャストできない場合にnull
を返す。
1
2
var n1 : Short ? = 123
var n2 : Int ? = n1 as ? Int ?
Copy 4. 関数4.1. 基本的な関数定義1
2
3
4
fun functionName ( param1 : Type1 , param2 : Type2 ): ReturnType {
// 関数の本体
return result
}
Copy 4.2. 単一式関数1
fun double ( x : Int ): Int = x * 2
Copy 4.3. デフォルト引数1
fun greet ( name : String = "Guest" ) = println ( "Hello, $name !" )
Copy 4.4. 名前付き引数1
2
fun createUser ( name : String , age : Int ) = User ( name , age )
val user = createUser ( age = 25 , name = "John" )
Copy 4.5. 可変長引数(vararg)1
fun sum ( vararg numbers : Int ): Int = numbers . sum ()
Copy 4.6. ローカル関数(関数内関数)1
2
3
4
fun outer () {
fun inner () = println ( "Inner function" )
inner ()
}
Copy 4.7. 拡張関数拡張関数は、既存のクラスに新しいメソッドを追加する機能。
1
fun String . addExclamation () = this + "!"
Copy 4.8. インライン関数インライン関数はコンパイル時に関数呼び出しの箇所に関数の中身が直接挿入される パフォーマンス最適化が目的 1
inline fun operation ( a : Int , b : Int , op : ( Int , Int ) -> Int ): Int = op ( a , b )
Copy 4.9. 高階関数関数を引数や戻り値として扱う関数の事。
1
2
fun operation ( x : Int , y : Int , op : ( Int , Int ) -> Int ): Int = op ( x , y )
val result = operation ( 10 , 20 , { a , b -> a + b })
Copy 4.10. トレーリングラムダ(末尾ラムダ)なお、高級関数の最後がラムダ式の場合は、そのラムダ式を括弧外に書ける。
1
2
fun operation ( x : Int , y : Int , op : ( Int , Int ) -> Int ): Int = op ( x , y )
val result = operation ( 10 , 20 ) { a , b -> a + b }
Copy 下のように引数に入れるべきものを外に書けるメリットがある。
1
list . filter { it > 0 }. map { it * 2 }
Copy Jetpack ComposeのsetContentは次のように使うが、これも末尾ラムダの例。
1
2
3
4
5
6
7
8
9
10
11
12
setContent {
AppTheme {
MainContent ()
}
}
// 下と同じ
setContent ({
EchoTimerTheme {
MainContent ()
}
})
Copy これは概ね次のように定義されている。
1
fun setContent ( content : @Composable () -> Unit )
Copy 4.11. ラムダ式{x -> xxx}
がそのまま関数になる。
1
val sum = { x : Int , y : Int -> x + y }
Copy 4.12. 関数型型は(int) -> int
のように定義する。
1
val multiply : ( Int , Int ) -> Int = { x , y -> x * y }
Copy 4.13. 演算子オーバーロード1
operator fun Point . plus ( other : Point ) = Point ( x + other . x , y + other . y )
Copy 4.14. 中置表記法DSLをKotlinで作れる機能 times
オペレーターが定義された1
2
infix fun Int . times ( str : String ) = str . repeat ( this )
val result = 3 times "Hello "
Copy 4.15. テイルレック最適化末尾再帰関数の定義も可能。
1
2
tailrec fun factorial ( n : Int , acc : Int = 1 ): Int =
if ( n <= 1 ) acc else factorial ( n - 1 , n * acc )
Copy 4.16. ジェネリック関数1
2
3
fun < T > printArray ( array : Array < T >) {
array . forEach { println ( it ) }
}
Copy 4.17. 多重戻り値KotlinにはTuple型はないため、戻り値が2つ、3つ、4つ以上で型を使い分ける 受け取る際は分解宣言して受け取る 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 2つ
fun returnPair (): Pair < Int , String > {
return Pair ( 42 , "Hello" )
}
// 3つ
fun returnTriple (): Triple < Int , String , Boolean > {
return Triple ( 42 , "Hello" , true )
}
// 4つ以上で別々の型
data class Result ( val value1 : Int , val value2 : String )
fun multipleValues (): Result {
return Result ( 42 , "Hello" )
}
// 4つ以上で同じ型
fun multipleValues2 (): List < Int > {
return listOf ( 1 , 2 , 3 , 4 , 5 )
}
Copy 4.18. UnitとNothing値を返却しない関数は Unit関数として扱われる。
1
2
3
fun printMessage ( msg : String ) {
println ( msg )
}
Copy 常に例外を返却し、戻り値を戻すことのない関数は Nothing型関数として定義される。
1
2
3
fun raiseError ( msg : String ): Nothing {
throw MyException ( msg )
}
Copy 4.19. 関数参照(::
)関数名の前に::
をつけると、関数を参照するオブジェクトを得ることができる。
1
2
3
4
5
6
fun add ( x : Int , y : Int ): Int = x + y
fun main () {
val method = :: add
println ( method ( 3 , 5 ))
}
Copy 5. 移譲と遅延(by
)5.1. 2つの委譲5.1.1. 2つの委譲とは次の委譲で、クラスのアクセッサーやメソッドを委譲する事が可能。
プロパティ委譲(Property Delegation) クラス委譲(Class Delegation) 5.1.2. プロパティ委譲個々のプロパティの振る舞いを別のオブジェクト(デリゲート)に委譲する by
キーワードを使用して実装するプロパティのgetter/setterの動作をカスタマイズするのに使用する 1
2
3
4
5
6
7
8
9
10
11
12
13
class Example {
var name : String by Delegate ()
}
class Delegate {
operator fun getValue ( thisRef : Any ?, property : KProperty <*>): String {
return " $thisRef , thank you for delegating ' ${property.name} ' to me!"
}
operator fun setValue ( thisRef : Any ?, property : KProperty <*>, value : String ) {
println ( " $value has been assigned to ' ${property.name} ' in $thisRef ." )
}
}
Copy 5.1.3. クラス委譲インターフェースの実装を別のオブジェクトに委譲する 継承の代替手段として使用される クラス宣言時にby
キーワードを使用して実装する 1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Printer {
fun print ()
}
class PrinterImpl ( val x : Int ) : Printer {
override fun print () { print ( x ) }
}
class SomeObject ( b : Printer ) : Printer by b
fun main () {
val b = PrinterImpl ( 10 )
SomeObject ( b ). print () // 10を出力
}
Copy SomeObjectのインスタンスのPrinterインターフェイスのメソッドはPrinterImplに委譲されているという事。
5.2. 遅延初期化5.2.1. 遅延初期化とは遅延初期化とは、オブジェクトの生成やプロパティの初期化を、それが実際に必要になるまで遅らせる技術。
遅延初期化の主な利点:
メモリ効率の向上 初期化コストの削減 循環依存の解決相互に依存するオブジェクト間の初期化問題を解決できる 5.2.2. lateinit
クラスのプロパティは初期化が必要だが、lateinit を用いることで初期化を遅らせることができる。
lateinit
には、次の制約がある。
Int などのプリミティブ型には使用できない Null許容型には使用できない var 変数にしか使用できない 1
2
3
4
class Foo {
lateinit var name : String // init()が呼ばれるまで初期化不要
fun init ( name : String ) { this . name = name }
}
Copy 5.2.3. by lazy
(デリゲート)lateinit
と似たものに by lazy
がある。
読み取り専用(val)プロパティに使用 最初にアクセスされたときに初期化され、その後は同じ値を返す スレッドセーフ 1
2
3
4
5
6
class Example {
val lazyValue : String by lazy {
println ( "Computed!" )
"Hello"
}
}
Copy クラスのプロパティだけではなく、ローカル変数も遅延初期化ができる。
1
2
3
4
val lazyValue : String by lazy {
println ( "computed!" )
"Hello"
}
Copy Observableの例。
1
2
3
var name : String by Delegates . observable ( "initial value" ) {
prop , old , new -> println ( " $old -> $new " )
}
Copy 6. Scoped Function6.1. Scoped Functionとはscoped functionは、オブジェクトのコンテキスト内で処理を行うための便利な関数群。
主に5つのスコープ関数がある。
let run with apply also 6.2. 5つの関数の違いそれぞれの違いをまとめると次になる。
関数名 オブジェクトの参照方法 戻り値 主な使用目的 let
it
ラムダの結果 null安全な呼び出し、ローカルスコープでの変数利用 run
this
ラムダの結果 オブジェクトの初期化と結果の計算 with
this
ラムダの結果 オブジェクトのプロパティやメソッドを繰り返し使用 apply
this
オブジェクト自体 オブジェクトの設定、初期化 also
it
オブジェクト自体 追加の効果や検証、ロギング
6.3. itとthisの違いit(暗黙の引数名)ラムダ式の引数として渡されるオブジェクトを指す オブジェクトを別の変数名で参照したい場合に便利 this(レシーバ)ラムダ式のレシーバとして渡されるオブジェクトを指す オブジェクトのメンバーに直接アクセスできる 1
2
3
4
5
6
7
8
9
10
11
// itの使用(let)
person . let {
println ( it . name ) // it.を使用
it . age = 30 // it.を使用
}
// thisの使用(apply)
person . apply {
println ( name ) // thisは省略可能
age = 30 // thisは省略可能
}
Copy 6.4. let
関数オブジェクトをラムダ式の引数として提供 戻り値はラムダ式の結果 主にnull安全な呼び出しに使用 1
val length = str ?. let { it . length } ?: 0
Copy 6.5. run
関数オブジェクトをレシーバ(this
)として提供 戻り値はラムダ式の結果 オブジェクトの初期化と結果の計算を同時に行う場合に便利 1
2
3
4
val result = person . run {
println ( name )
age * 2
}
Copy 6.6. with
関数オブジェクトをレシーバ(this
)として提供 戻り値はラムダ式の結果 オブジェクトのプロパティやメソッドを繰り返し使用する場合に便利 1
2
3
4
with ( person ) {
println ( name )
println ( age )
}
Copy 6.7. apply
関数オブジェクトをレシーバ(this
)として提供 オブジェクト自体を返す オブジェクトの設定に便利 1
2
3
4
val person = Person (). apply {
name = "John"
age = 30
}
Copy 6.8. also
関数オブジェクトをラムダ式の引数として提供 オブジェクト自体を返す 追加の効果や検証に使用 1
2
3
val numbers = mutableListOf ( 1 , 2 , 3 )
numbers . also { println ( "The list elements are: $it " ) }
. add ( 4 )
Copy 7. Class7.1. コンストラクタ(constructor
)7.1.1. 2種類のコンストラクタKotlin のクラスには2種類のコンストラクタがある。
プライマリコンストラクタ(クラスに1つだけ) セカンダリコンストラクタ(0個以上持つことができる) 7.1.2. プライマリコンストラクタ7.1.2.1. プライマリーコンストラクタの構文クラス名に直接指定する事で、プライマリーコンストラクタの引数を設定できる プライマリーコンストラクタは、以下のように記述する(constructorは省略するのが一般的) 1
class クラス名 constructor ( 引数 : デ ー タ型 ) {}
Copy 7.1.2.2. プライマリーコンストラクタのパラメータープライマリーコンストラクタのパラメータでval
やvar
を使用すると、自動的にクラスのプロパティとして定義される また、プロパティにアクセス修飾子やデフォルト引数も付与できる プライマリーコンストラクタ or プライマリーコンストラクタのパラメータと呼ぶ 1
class Person ( private val name : String , public var age : Int = 10 )
Copy 7.1.2.3. プライマリーコンストラクタの本体プライマリーコンストラクタの本体はinit
で定義する このinit
でプライマリーコンストラクタのパラメータ処理をしていた場合は実質プライマリーコンストラクタの本体 1
2
3
4
5
6
7
class Person ( val name : String , var age : Int ) {
init {
require ( age >= 0 ) { "Age must be non-negative" }
name = "bob"
age = 10
}
}
Copy 7.1.3. セカンダリコンストラクタ7.1.3.1. セカンダリコンストラクタの構文セカンダリーコンストラクタはクラス内にconstructor
メソッドを使って定義するコンストラクタ 一般的なコンストラクタとの違いはthis(引数)
構文によって、直接or間接的にプライマリーコンストラクタを呼ばなければならない点 セカンダリーコンストラクタの引数は、プライマリーコンストラクタの引数パラメータ(this(引数1, 引数2)
)に渡される必要がある 1
constructor ( 引数 : デ ー タ型 ): this ( 引数 )
Copy 1
2
3
4
class Person ( var name : String , var age : Int ) {
// name=引数のname, age=10でプライマリーコンストラクタに渡される
constructor ( name : String ) : this ( name , 10 )
}
Copy 7.1.3.2. 暗黙的なプライマリーコンストラクタ一般的なクラス定義(this(引数)
)抜きをすると、暗黙的なプライマリーコンストラクタとみなされる。
1
2
3
4
5
6
7
8
9
class Person {
var name : String
var age : Int
constructor ( name : String , age : Int ) {
this . name = name
this . age = age
}
}
Copy 7.1.3.3. 直接呼び出し、関節呼び出しKotlinでは、セカンダリーコンストラクタは必ず直接的または間接的にプライマリーコンストラクタを this(引数)
で呼び出す必要がある。
直接的な呼び出し
1
2
3
4
5
6
7
8
class Person ( val name : String ) {
var age : Int = 0
// ここのthis(name)でプライマリーコンストラクタを呼び出している
constructor ( name : String , age : Int ) : this ( name ) {
this . age = age
}
}
Copy 間接的な呼び出し
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person ( val name : String ) {
var age : Int = 0
var email : String = ""
constructor ( name : String , age : Int ) : this ( name ) {
this . age = age
}
// ここが2つめのコンストラクタを使った間接的なプライマリーコンストラクタの呼び出し
constructor ( name : String , age : Int , email : String ) : this ( name , age ) {
this . email = email
}
}
Copy 7.2. 初期化ブロック(init
)7.2.1. 初期化ブロックとは初期化ブロックは、プロパティの初期化を行うためのブロック クラスに init { ... }
で初期化ブロックを記述する 初期化ブロックはコンストラクタが呼び出されるよりも前に呼び出される 1
2
3
4
5
class Foo {
init {
println ( "Foo is created." )
}
}
Copy 7.2.2. なぜ初期化ブロック(init
)が必要かそれはプライマリーコンストラクタがあるから プライマリーコンストラクタは実質的にコンストラクタの引数部分のみを宣言する 故にプライマリーコンストラクタの関数の本体としてinitが必要という事 1
2
3
4
5
6
7
8
9
10
11
class Person ( val name : String , val age : Int = 0 ) { // ここがプライマリーコンストラクタの引数
// このinitがプライマリーコンストラクタの本体
init {
require ( age >= 0 ) { "Age must be non-negative" }
}
}
// 使用例
val person1 = Person ( "Alice" , 30 )
val person2 = Person ( "Bob" ) // ageはデフォルト値の0が使用される
Copy 7.2.3. コンストラクタと初期化ブロックの違いコンストラクタと初期化ブロックには以下のような違いがある。
承知しました。初期化ブロックとコンストラクターの主な違いを以下の表にまとめました。
特徴 初期化ブロック コンストラクター 定義方法 init
キーワードを使用クラス名の後(プライマリ)またはconstructor
キーワード(セカンダリ) 主な目的 共通の初期化ロジック オブジェクト作成時の特定の初期化 パラメータ 直接受け取れない 受け取ることができる 実行タイミング 記述(宣言)前のプロパティの初期化の後 オブジェクト作成時 複数定義 可能(定義順に実行) 可能(オーバーロード) 再利用性 すべてのコンストラクターで共通 特定の初期化シナリオに対応 this キーワード 使用可能 使用可能(他のコンストラクター呼び出しにも使用) 戻り値 なし なし(暗黙的にオブジェクトインスタンスを返す) アクセス修飾子 不要 必要に応じて使用可能 プロパティ初期化との関係 プロパティ初期化と交互に実行 プロパティ初期化の後に実行
7.3. メンバの宣言順序と実行順序の注意7.3.1. 実行順番のルールプロパティの初期化は次の3つの順番で行われる。
プライマリーコンストラクタのパラメータの評価 これはセカンダリーコンストラクタのthis()
呼び出しによるモノも含む プロパティの初期化 と初期化ブロック (init
)のコード宣言準の実行これには、プライマリーコンストラクタで宣言されたプロパティも含む プロパティと初期化ブロックは宣言された順序で交互に実行される セカンダリーコンストラクタの本体の実行(もし呼び出されていれば) 7.3.2. 実行順序の例 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Example ( val input : String ) { // プライマリーコンストラクタの引数
// ファーストプロパティ
val first = println ( "First property" )
// ファーストイニシャライザ
init {
println ( "First initializer block: $input " )
}
// セカンダリープロパティ
val second = println ( "Second property" )
// セカンダリーイニシャライザ
init {
println ( "Second initializer block" )
}
// セカンダリーコンストラクタ
constructor ( input : String , second : Int ): this ( input ) {
println ( "Secondary constructor" )
}
}
Copy プライマリーコンストラクタの実行結果
プライマリーコンストラクタが実行されるため、InputとしてHelloが入る その後に、プロパティとinitが宣言順に評価される セカンダリーコンストラクタはもちろん呼ばれない 1
2
3
4
5
6
7
8
fun main () {
Example ( "Hello" )
}
First property
First initializer block : Hello
Second property
Second initializer block
Copy セカンダリーコンストラクタの実行結果
プライマリーコンストラクタ(this(引数)
)が最初に評価される その後に、宣言順にプロパティとinitが実行される 最終的にセカンダリーコンストラクタが実行される 1
2
3
4
5
6
7
8
9
fun main () {
Example ( "Hello" , 5 )
}
First property
First initializer block : Hello
Second property
Second initializer block
Secondary constructor
Copy 7.4. アクセッサーメンバ変数に添えて、インデントがずれたような形でgetter/setterを定義できる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Foo () {
var name : String = ""
set ( value ) {
println ( "__set__" )
field = value
}
get () {
println ( "__get__" )
return field
}
}
fun main () {
var a = Foo ()
a . name = "Yamada" // __set__
println ( a . name ) // __get__
}
Copy 7.5. data class1
data class User ( val name : String , val age : Int )
Copy 7.6. extendskotlinのクラスはデフォルトで継承不可能 そのため、open
キーワードをつける 1
2
3
4
5
6
7
8
9
open class Animal {
open fun makeSound () { println ( "Some sound" ) }
fun eat () { println ( "Eating" ) } // このメソッドはオーバーライド不可
}
class Dog : Animal () {
override fun makeSound () { println ( "Bark" ) } // OK
// override fun eat() { } // コンパイルエラー
}
Copy ただし、抽象クラスはデフォルトでopen
。
1
2
3
abstract class Shape {
abstract fun draw () // 抽象メソッドは自動的にopen
}
Copy :
と親クラス名を指定して継承する。
1
2
open class Animal ( val name : String )
class Dog ( name : String ) : Animal ( name )
Copy なお、親クラスが引数なしのコンストラクタ(または引数がすべてデフォルト値を持つコンストラクタ)を持つ場合、括弧を省略できる。
1
2
open class Animal
class Dog : Animal
Copy 明示的に呼び出したい場合は空の括弧を使用できる。
1
2
open class Animal
class Dog : Animal ()
Copy 7.7. implementsinterfaceはabstrct classと同様にopen kotlinのinterfaceは抽象プロパティも定義可能 1
2
3
4
5
6
7
8
interface Speakable {
fun speak ()
}
class Person : Speakable {
override fun speak () {
println ( "Hello!" )
}
}
Copy 7.8. sealed class制限された階層構造を定義するために使用される特別な種類のクラス sealedクラスのサブクラスは、同じファイル内か、ネストされたクラスとして定義する必要がある when式でsealedクラスを使用する場合、コンパイラは全てのサブクラスが処理されているかチェックする 例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sealed class ApiResult < out T > {
data class Success < out T >( val data : T ) : ApiResult < T >()
data class Error ( val message : String ) : ApiResult < Nothing >()
object Loading : ApiResult < Nothing >()
}
fun handleApiResult ( result : ApiResult < String >) {
when ( result ) {
is ApiResult . Success -> println ( "成功: ${result.data} " )
is ApiResult . Error -> println ( "エラー: ${result.message} " )
is ApiResult . Loading -> println ( "読み込み中..." )
}
}
// 使用例
val successResult = ApiResult . Success ( "データ取得成功" )
val errorResult = ApiResult . Error ( "ネットワークエラー" )
val loadingResult = ApiResult . Loading
handleApiResult ( successResult )
handleApiResult ( errorResult )
handleApiResult ( loadingResult )
Copy 7.9. innnerクラスinner
はネストされたクラスが内部クラスであることを示す。
1
2
3
4
5
class A {
inner class B { .. . }
}
var obj = A (). B ()
Copy 8. Generics8.1. 型制約Genericsでも型に制約をかけることができる 下記では、IntやDoubleなど、Numbe のサブクラスに制約している 1
2
3
4
5
6
class Foo < T : Number >( val value : T )
fun main () {
val a1 = Foo ( 123 )
println ( a1 . value + 1 ) // 124
}
Copy 8.2. outout キーワードはサブクラスをスーパークラスに入れられる。
Javaでは以下のGenericsのUp Castは許されていない。
1
2
3
4
5
6
7
8
9
// Java
interface Nextable < T > {
T nextT ();
}
void demo ( Nextable < String > strs ) {
Nextable < Object > objects = strs ; // !!! Not allowed in Java
// ...
}
Copy Kotlinはそれができる。
1
2
3
4
5
6
7
8
interface Nextable < out T > {
fun nextT (): T
}
fun demo ( strs : Nextable < String >) {
val objects : Nextable < Any > = strs // This is OK
// ...
}
Copy 8.3. inin キーワードはスーパークラスをサブクラスに入れられる。
1
2
3
4
5
6
7
8
9
interface Comparable < in T > {
operator fun compareTo ( other : T ): Int
}
// NOTE: Numberのsubtypeはdouble
fun demo ( x : Comparable < Number >) {
x . compareTo ( 1.0 )
val y : Comparable < Double > = x // ok
}
Copy 8.4. inとoutの違いin
とout
の違いは次になる。
特徴 in
(反変性)out
(共変性)キーワード in
out
変性 反変性(Contravariance) 共変性(Covariance) 主な用途 コンシューマー(消費者) プロデューサー(生産者) 許可される操作 引数として受け取る(入力) 戻り値として返す(出力) 禁止される操作 戻り値として返す(出力) 引数として受け取る(入力) 型の関係 スーパータイプを受け入れる サブタイプを返すことができる 代入の方向 スーパータイプからサブタイプへ サブタイプからスーパータイプへ 安全性の保証 入力の型安全性 出力の型安全性 コード例 interface Comparable<in T>
interface List<out T>
9. Object9.1. Object式(匿名クラス)Object式は、Kotlinで匿名クラスを作成するための機能 Java の匿名内部クラスに相当する 1
2
3
4
val myObject = object {
val property = "Hello"
fun method () = "World"
}
Copy インターフェイスの実装
1
2
3
4
5
6
7
8
9
10
interface Greeter {
fun greet ()
}
val englishGreeter = object : Greeter {
override fun greet () {
println ( "Hello!" )
}
}
Copy 9.2. Object宣言(シングルトン)Kotlinのobject宣言を使用すると、シングルトンオブジェクトを簡単に作成できる。
1
2
3
4
5
6
7
8
9
10
11
object MySingleton {
val CONSTANT_VALUE = 42
fun printHello () {
println ( "Hello from Singleton" )
}
}
fun main () {
println ( MySingleton . CONSTANT_VALUE )
MySingleton . printHello ()
}
Copy 9.3. Companion Object(Static Member)companion objectは、Kotlinの特有の機能で、Javaのstaticメンバーに似た機能を提供する Kotlinでは静的メンバーという概念が直接存在しないため、companion objectを使ってこれを実現する コンパニオンオブジェクトの例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass {
companion object {
const val CONSTANT_VALUE = 42
fun printHello () {
println ( "Hello from Companion Object" )
}
}
}
fun main () {
println ( MyClass . CONSTANT_VALUE ) // 42
MyClass . printHello () // Hello from Companion Object
}
Copy 名前付きコンパニオンオブジェクトの例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass {
companion object NamedCompanion {
const val CONSTANT_VALUE = 42
fun printHello () {
println ( "Hello from Named Companion Object" )
}
}
}
fun main () {
println ( MyClass . NamedCompanion . CONSTANT_VALUE ) // 42
MyClass . NamedCompanion . printHello () // Hello from Named Companion Object
}
Copy 10. Null対応10.1. AnyKotlinのクラスは全て「Any」というクラスを継承している Anyクラスは非Null型として定義された全てのクラスのスーパークラス つまり、Kotlinのクラスは全て非Null型になる 故に、KotlinではNull許容型であるJavaのObjectのサブクラスとなることはできない 10.2. ?
付きタイプ?
を型につけて宣言する事でnullable、オプショナル、null Unionな型にできる。
1
2
var a : String ? = "はろー" //String型の変数
a = null // ok
Copy 10.3. .?
演算子セーフコールでアクセスするための演算子。
1
2
3
4
5
var d : String ? = null
println ( d ?. length ) //エラーは起きない。「null」が表示される
var e : String ? = "はろはろ"
println ( e ?. length ) //「4」が表示される
Copy 10.4. :?
演算子:?演算子(エルビス演算子)は参考演算子を短くしたようなデフォルト付きの演算子。
1
2
3
//?:演算子でgetName関数の結果がnullだった場合の値を設定
val name2 : String = getName () ?: "名なしの権兵衛"
println ( name2 )
Copy 10.5. !!
演算子NullPointerException
を発生する形でアクセスするための演算子。
1
2
3
4
//!!演算子を使ってnullの入ったNull許容型を無理やり非Null型に変換して参照
var f : String ? = null
//コンパイルはできるが、実行時にめでたくNullPointerException発生
println ( f !! . length )
Copy 11. Coroutineのアーキ11.1. Structured Concurrency11.1.1. Structured ConcurrencyとはStructured Concurrency(構造化並行処理)はプログラミング言語における並行処理、非同期処理のAPIのパラダイムの一つ 並行処理をより安全で予測可能にし、エラー処理や資源管理を改善することを目的としている KotlinはStructured Concurrencyを採用している 以下が特徴となる。
階層構造:コルーチンは親子関係を持つ階層構造で組織される 子コルーチンは親コルーチンのコンテキスト内で実行される ライフサイクル管理:親コルーチンは、全ての子コルーチンが完了するまで終了しない これにより、処理の完了を確実に把握できる エラー伝播:子コルーチンで発生した未処理の例外は、親コルーチンに自動的に伝播する これにより、エラー処理が簡素化される キャンセル伝播:親コルーチンがキャンセルされると、そのすべての子コルーチンも自動的にキャンセルされる スコープ:coroutineScope や supervisorScope などの関数を使用して、明示的にスコープを定義できる 11.1.2. Structured Concurrencyの例coroutineScope 内で2つの非同期処理(async)を開始している coroutineScope は、すべての子コルーチンが完了するまで終了しない いずれかの子コルーチンで例外が発生した場合、他の子コルーチンはキャンセルされ、例外が親に伝播する 1
2
3
4
5
6
7
8
9
10
11
12
suspend fun fetchUserData () = coroutineScope {
val userDeferred = async { fetchUser () }
val friendsDeferred = async { fetchFriends () }
try {
val user = userDeferred . await ()
val friends = friendsDeferred . await ()
// 両方のデータの処理
} catch ( e : Exception ) {
// エラー処理
}
}
Copy 11.2. 平行制御11.2.1. 4つの平行制御とはKotlinのコルーチンの平行制御には次の4つのタイプがある。
Channel複数のコルーチン間でデータや信号を交換するためのパイプラインとして機能する 生産者-消費者パターンに適している Flow非同期に計算される値のシーケンスを表現する データ変換、フィルタリング、結合などの操作が容易 Mutex一度に1つのコルーチンのみがクリティカルセクションにアクセスできるようにする 共有リソースへの同時アクセスを防ぐ Semaphore指定された数のコルーチンが同時にリソースにアクセスできるようにする リソースプールの管理に適している 11.2.2. 4つの平行制御の比較4つの平行制御があり、それぞれの違いは以下。
特性 Channel Flow Mutex Semaphore 主な用途 コルーチン間の通信 非同期データストリーム処理 相互排他制御 リソースへのアクセス制限 データの流れ 生産者から消費者へ 上流から下流へ N/A N/A ホット/コールド ホット コールド(デフォルト) N/A N/A バックプレッシャー 自然にサポート 明示的に実装可能 N/A N/A 複数の受信者 通常1つ(ファンアウトも可能) 複数可能 N/A N/A 並行アクセス 安全 安全 1つのコルーチンのみ 指定数のコルーチン キャンセル伝播 サポート サポート N/A N/A バッファリング 可能 可能 N/A N/A 主な操作 send/receive emit/collect lock/unlock acquire/release 用途例 タスクキュー、イベント配信 UI更新、データ変換 共有リソースの保護 接続プールの制御
11.3. Coroutineの種類11.3.1. Coroutineの種類KotlinのCoroutinesは次を提供する。
名前 関数 返す型 ワンショット(戻り値なし) launch{}
Job
ワンショット(戻り値あり) async{}
Deferred<T>
ホットストリーム(複数の値) Channel
Channel<T>
コールドストリーム(複数の値) Flow
Flow<T>
11.3.2. ワンショットワンショットとは一度だけ実行される関数の事。
11.3.3. ホットストリームとコールドストリームの違いホットストリームコレクターの有無に関わらず、データを生成し続ける 常にアクティブで、リソースを継続的に消費する コールドストリームコレクターが要求したときのみデータを生成する 必要なときのみリソースを使用し、それ以外はアイドル状態となる 11.4. FlowとChannel11.4.1. FlowとChannelの違いFlwoとChannelをよく使うが次の点が大きく違う。
Channelアクター送信者(sender)と受信者(receiver)の関係 関係 消費モデル競合的な消費モデル 送信された値は、1つの受信者によってのみ消費される つまり、複数のreceiverがいたら早い者勝ちになる Flowアクター生産者(emitter)と収集者(collector)の関係 関係 消費モデル非競合的な消費モデル 送信された値は、全ての受信者に届く つまり、複数のreceiverがいても確実に届く 11.4.2. FlowとChannneの図Channelは値は早いもの勝ちになる channelのキューの値を取得する 複数の受信者がある場合、各値は1つの受信者にのみ届く
Channnel Flowの値は全てのCollectorに届く 各Collectorは独立してFlowの全シーケンスを受け取る 複数のCollectorが同じFlowをcollectできるが、それぞれ独立した実行となる
Flow SharedFlowは、分かりやすく言うと、Channelの非競合的な消費モデル版 実際的にはboradcast channelのように機能する collect/collectorではなく、subscribe/subscriberと呼ばれる
SharedFlow 11.5. Flow11.5.1. Flwoのアクターコレクター(Collector)ストリームのコレクター(Collector)は、ストリーム処理の終端操作の一つ ストリームの要素を集約して最終的な結果を生成するための機能 collect()
メソッドを使用する エミッター(Emitter):データを生成し、ストリームに送り出す役割を果たす 例: emit()
関数を使用してデータを送信する 11.5.2. FlowとはFlowとは非同期に計算される値のシーケンスを表現するインターフェース 収集者が要求したときのみデータを生成する(コールドストリーム) 1
2
3
4
5
6
flow {
for ( i in 1. . 3 ) {
delay ( 100 )
emit ( i )
}
}. collect { value -> println ( value ) }
Copy 11.5.3. StateFlowとMutableStateFlowとはStateFlowの主な特徴
常に値を保持 ホットストリーム 値の重複排除同じ値が連続して設定されても、収集者には通知されない 最新値の即時提供新しい収集者は即座に最新の値を受け取る stateFlowはcollectしたら必ずそのcollectした瞬間に一回データが流れてくる MutableStateFlowの特徴
値の変更が可能 その他の特徴はStateFlowと同じ(常に値を保持、ホットストリーム、値の重複排除など) 11.5.4. StateFlowの例以下はAndroidでViewModelとStateFlowを使ったFlowの例。
1
2
3
4
5
6
7
8
9
10
11
12
13
// privateで変えられる、setter用
private val _keepScreenOn = MutableStateFlow ( settingsPreferences . getBoolean ( "keepScreenOn" , false ))
// publicで変えられない、getter用
val keepScreenOn : StateFlow < Boolean > = _keepScreenOn
// 利用
lifecycleScope . launch { // AndroidのLifecycleのScope
viewModel . keepScreenOn . collect { keepOn -> // collectorの処理となる
if ( keepOn ) {
xxx
}
}
}
Copy 11.5.5. Flowの種類Flow, SharedFlow, StateFlowなどの違いは以下。
Flowのベン図 11.5.6. 複数のcollectの注意点1つのコルーチンビルダーで複数のflowをcollectすると、2個目以降はcollectされないので注意。
1つのcollectの例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main () = runBlocking {
val scope = CoroutineScope ( Dispatchers . Default )
val myStateFlow = MutableStateFlow ( 0 )
scope . launch {
myStateFlow . collect { value ->
println ( "Collected value: $value " )
}
}
delay ( 200 )
// 出力: Collected value: 0 (即座に現在の値が発行される)
myStateFlow . value = 1
delay ( 200 )
// 出力: Collected value: 1
myStateFlow . value = 1
delay ( 200 )
// 重複値なので発行されない
myStateFlow . value = 2
delay ( 200 )
// 出力: Collected value: 2
}
Copy その結果
1
2
3
Collected value: 0
Collected value: 1
Collected value: 2
Copy 2つのcollectの例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main () = runBlocking {
val scope = CoroutineScope ( Dispatchers . Default )
val myStateFlow = MutableStateFlow ( 0 )
val myStateFlow2 = MutableStateFlow ( 0 )
scope . launch {
myStateFlow . collect { value ->
println ( "Collected myStateFlow: $value " )
}
myStateFlow2 . collect { value ->
println ( "Collected myStateFlow2: $value " )
}
}
delay ( 200 )
// 出力: Collected myStateFlow: 0 (即座に現在の値が発行される)
myStateFlow . value = 1
delay ( 200 )
// 出力: Collected myStateFlow: 1
myStateFlow . value = 1
delay ( 200 )
// 重複値なので発行されない
myStateFlow . value = 2
delay ( 200 )
// 出力: Collected myStateFlow: 2
myStateFlow2 . value = 1
delay ( 200 )
delay ( 2000 )
}
Copy その結果(myStateFlow2の現在の値とcollectが発生していない)
1
2
3
Collected myStateFlow: 0
Collected myStateFlow: 1
Collected myStateFlow: 2
Copy myStateFlow2が動かない理由は、最初のcollectが完了(この場合は永続的な監視を終了)するまで、次のcollectは開始されないから。
11.6. Channel11.6.1. Channelのアクタープロデューサー(Producer):特にChannelにおいて、データを生成する側を指す send()
メソッドを使用してデータを送信する コンシューマー(Consumer):データを受け取って処理する側を指す Collectorと同義で使われることもある 11.6.2. ChannnelとはChannelは、Kotlinのコルーチンライブラリで提供される、非同期通信のためのプリミティブ 主に複数のコルーチン間でデータを安全に受け渡すために使用される Flowはコールドストリームだったが、Channelはホットストリーム 主な特徴:
ホットストリーム スレッドセーフ バッファリング バックプレッシャー(back pressure) 11.6.3. Channelの種類Channelにはいくつかの種類がある。
Rendezvous (容量0) Buffered Unlimited Conflated 11.6.4. チャンネルの例チャネル(Channel)とは、コルーチン間の通信に使用する。
1
2
3
4
5
6
7
8
9
10
11
12
val channel = Channel < Int >()
launch {
for ( i in 1. . 5 ) {
channel . send ( i )
}
channel . close ()
}
for ( value in channel ) {
println ( value )
}
Copy 11.7. suspend11.7.1. suspendとはsuspendキーワードはcoroutineの中核のキーワード。
形式:suspendは関数修飾子suspend func getXXX(): String {}
みたいに使用する 目的:非同期関数を定義するJSやPythonでいうasyncキーワードと同じ 特徴:
関数自体を中断可能(suspendable
)にする 他のsuspend関数やCoroutineBuilder内でのみ呼び出せる 結果を直接返す(非同期的に) 11.7.2. suspend関数suspend関数は、Kotlinのコルーチンシステムの中核をなす重要な概念 suspend関数はsuspendキーワードを使って定義される関数で、実行を一時停止し、後で再開することができる JSでいう、async
関数のようなもの 1
2
3
4
5
6
suspend fun fetchUserData (): UserData {
// 非同期処理
val userId = fetchUserId () // サスペンドポイント1
delay ( 100 ) // サスペンドポイント2
return fetchUserDetails ( userId ) // サスペンドポイント3
}
Copy 11.7.3. launchやasyncとsuspendの違いasyncとlaunchは関数で、suspendは関数修飾子。
特性 async/launch suspend 使用方法 直接呼び出して新しいコルーチンを開始する 関数を定義する際に使用し、その関数を中断可能にする コンテキスト CoroutineScope内でのみ使用可能 定義時に使用し、他のsuspend関数やコルーチン内から呼び出される 目的 新しいコルーチンを作成し、並行処理を開始する 既存の関数を非同期的に実行可能にする
11.7.4. suspendとdelay()
、await()
suspend修飾子を使用すると、delay()
やawait()
などの中断可能な関数を使用できるようになる これらの関数は「サスペンドポイント」として機能し、コルーチンの実行を一時的に中断し、後で再開することができるようになる サスペンドポイント
通常、他のサスペンド関数を呼び出す箇所がサスペンドポイントとなる 例えば、delay()
、await()
、 他のカスタムサスペンド関数の呼び出しなど 違い
delay()
指定した時間だけコルーチンの実行を中断する スレッドをブロックせずに待機できる await()
Deferred<T>
の結果を待つために使用する結果が利用可能になるまでコルーチンを中断する 11.7.5. suspend関数の例以下がdelay
とawait
を使った例。
1
2
3
4
5
6
7
8
9
10
suspend fun fetchUserData (): UserData {
delay ( 1000 ) // ネットワーク遅延をシミュレート
return UserData ( "John" , 30 )
}
suspend fun processUser () {
val deferred = async { fetchUserData () }
val userData = deferred . await () // 結果が得られるまで中断
println ( "User: ${userData.name} " )
}
Copy 12. Coroutineの使い方12.1. 3つのSteps12.1.1. 3つのCoroutineのSteps大まかに言うと次の3つの流れでコルーチンを使う。
ディスパッチャーの決定(どのスレッドを使うか) スコープの決定(どのライフサイクルを使うか) ビルダーの決定(どんなコルーチン処理をするか) 12.1.2. 3つのStepsの例次のViewModelの例はフローに従っている。
ディスパッチャー:Dispatchers.IO(ネットワーク呼び出し)とDispatchers.Main(UI更新)を使用 スコープ:viewModelScope(ViewModelのライフサイクルに紐づく) ビルダー:launch(結果を直接返さない処理) 1
2
3
4
5
6
7
8
9
10
class MyViewModel : ViewModel () {
fun loadData () {
viewModelScope . launch ( Dispatchers . IO ) {
val result = performNetworkCall ()
withContext ( Dispatchers . Main ) {
updateUI ( result )
}
}
}
}
Copy 12.2. CoroutineDspatcher12.2.1. CoroutineDspatcherとはCoroutineDspatcherは、コルーチンがどのスレッドまたはスレッドプールで実行されるかを決定する コンポーネント 扱うタスクの性質に応じてディスパッチャーを選択する必要がある 12.2.2. CoroutineDspatcherの種類Dispatchers.DefaultCPU集約型のタスクに適している バックグラウンドスレッドプールを使用する 複数のスレッドで並行実行される可能性が高い Dispatchers.MainUI操作に適している(Android、JavaFXなど) メインスレッド(UIスレッド)上で実行される 同時に1つのタスクのみが実行される Dispatchers.IO 12.2.3. CoroutineDspatcherの例次のように使用する。
1
2
3
4
5
6
7
8
9
10
11
12
launch ( Dispatchers . IO ) {
// I/O操作を行うコード
}
withContext ( Dispatchers . Default ) {
// CPU集約型の処理
}
val deferredCPU = async ( Dispatchers . Default ) {
// CPU集約型の処理
}
val resultCPU = deferredCPU . await ()
Copy 12.3. CoroutineScope12.3.1. CoroutineScopeとはCoroutineScopeは、コルーチンの実行範囲を定義し、その生存期間を管理するためのコンテキスト キャンセル処理やポーズなどのコンテキストを決めるスコープ Pythonで言うところのリソースの自動開放をするwith
キーワードに近い 1
2
3
4
with open ( 'file.txt' , 'r' ) as file :
content = file . read ()
print ( content )
# このブロックを抜けると自動でclose
Copy 12.3.2. CoroutineScopeの種類GlobalScope CoroutineScope lifecycleScopeAndroid の LifecycleOwner に紐づいたスコープ viewModelScope 12.3.3. CoroutineScopeが必要な理由CoroutineScopeは非同期処理の管理、制御、およびリソースの効率的な利用を可能にする為に必要。
構造化並行性コルーチンの階層構造を管理し、親子関係を確立する 親スコープがキャンセルされると、子コルーチンも自動的にキャンセルされる ライフサイクル管理コルーチンの開始と終了を制御する アプリケーションの特定のライフサイクル(例:Androidのアクティビティ)に合わせてコルーチンを管理できる リソース管理コルーチンが使用するリソースを適切に解放する メモリリークを防ぐために重要 12.3.4. CoroutineScopeの使用例Dispatchers.Default
はCPU 集約型タスクのために最適化されたスレッドプールを使用するディスパッチャーつまり、Dispatcherを指定する事でコルーチンのスレッドを決定する 1
2
3
4
5
6
7
8
9
10
11
val scope = CoroutineScope ( Dispatchers . Default )
scope . launch {
// この中で複数のコルーチンを起動できる
val result1 = async { fetchData1 () }
val result2 = async { fetchData2 () }
println ( "Results: ${result1.await()} , ${result2.await()} " )
}
// スコープのキャンセル(必要に応じて)
scope . cancel ()
Copy launchもasyncもCoroutineBuilder関数 一つの親コルーチン(launch)の中に、2つの子コルーチン(async)が存在する形になる 12.4. CoroutineBuilder12.4.1. CoroutineBuilderとはCoroutineBuilderは、Kotlinのコルーチンを作成し、開始するための関数 これらは新しいコルーチンを構築し、その実行を制御する
-launch
, async
などがある 12.4.2. CoroutineBuilderの使い方launch
結果を返さない新しいコルーチンを開始する Job オブジェクトを返す(キャンセルなどの制御に使用) fire and forget
(実行しっぱなし)タイプの非同期タスクに適している1
2
3
val job = launch {
// コルーチンの処理
}
Copy async
結果を返すコルーチンを開始する。 Deferred<T>
でT型の戻り値のオブジェクトを返すawait()
メソッドで結果を取得できる1
2
3
4
val deferred = async {
return 42
}
val result = deferred . await () // 42
Copy runBlocking
現在のスレッドをブロックし、その中でコルーチンを実行する 主にテストや main 関数でコルーチンを実行する際に使用 1
2
3
4
runBlocking {
delay ( 1000L )
println ( "After delay" )
}
Copy coroutineScope
新しいCoroutineScopeを作成し、すべての子コルーチンが完了するまで待機する サスペンド関数内で使用され、親コルーチンのコンテキストを継承する 1
2
3
4
suspend fun doSomething () = coroutineScope {
launch { task1 () }
launch { task2 () }
}
Copy supervisorScope
coroutineScope と似ているが、子コルーチンの例外が他の子や親に伝播しない エラー耐性のある操作に適している 1
2
3
4
supervisorScope {
launch { riskyTask1 () }
launch { riskyTask2 () }
}
Copy withContext
指定されたコンテキストで、コルーチンのブロックを実行する 主にディスパッチャーを切り替えるのに使用 1
2
3
withContext ( Dispatchers . IO ) {
// I/O操作
}
Copy 12.4.3. CoroutineBuilderの使い分けいくつかCoroutineBuilderがあるが、次のように使い分ける。
ビルダー 主な用途 戻り値 特徴 典型的な使用場面 launch 結果を返さない非同期タスク Job 「発射して忘れる」タイプの処理 バックグラウンド処理、ログ記録、イベント送信 async 結果を返す非同期タスク Deferred 並列処理、結果の取得が必要な場合 複数のAPIリクエスト、並列計算 runBlocking メイン関数やテストでのコルーチン実行 T (ブロック内の最後の式の結果) 現在のスレッドをブロック テスト、スクリプトのメイン関数 coroutineScope 複数の非同期操作のグループ化 T (ブロック内の最後の式の結果) すべての子が完了するまで待機 関連する複数の非同期タスクの実行 supervisorScope エラー耐性のある操作 T (ブロック内の最後の式の結果) 子の失敗が他に影響しない 独立した複数のタスク実行、エラー分離が必要な場合 withContext 特定のコンテキストでの実行 T (ブロック内の最後の式の結果) コンテキスト(主にディスパッチャー)の切り替え I/O操作、計算集約型タスク、UI更新
13. その他13.1. アクターモデルアクターモデルの基本概念アクターは独立した計算単位で、自身の状態を持ち、メッセージを受け取って処理する アクター同士は直接通信せず、メッセージパッシングを通じて相互作用する 各アクターは独自のメールボックス(メッセージキュー)を持つ kotlinでのアクターモデルの例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
sealed class CounterMsg
object IncCounter : CounterMsg ()
class GetCounter ( val response : CompletableDeferred < Int >) : CounterMsg ()
fun CoroutineScope . counterActor () = actor < CounterMsg > {
var counter = 0 // アクターの状態
for ( msg in channel ) { // メッセージの受信ループ
when ( msg ) {
is IncCounter -> counter ++
is GetCounter -> msg . response . complete ( counter )
}
}
}
suspend fun main () = coroutineScope {
val counter = counterActor () // アクターの作成
withContext ( Dispatchers . Default ) {
massiveRun { counter . send ( IncCounter ) } // 多数の増加メッセージを送信
}
// 最終的なカウンターの値を取得
val response = CompletableDeferred < Int >()
counter . send ( GetCounter ( response ))
println ( "Counter = ${response.await()} " )
counter . close () // アクターを終了
}
suspend fun massiveRun ( action : suspend () -> Unit ) {
val n = 100 // 起動するコルーチンの数
val k = 1000 // 各コルーチンで実行する繰り返しの回数
val time = measureTimeMillis {
coroutineScope {
repeat ( n ) {
launch {
repeat ( k ) { action () }
}
}
}
}
println ( "Completed ${n * k} actions in $time ms" )
}
Copy 14. 参考文献