Kotlin
Kotlinは、JetBrains社によって開発された静的型付けされたオブジェクト指向のプログラミング言語で、Java仮想マシン(JVM)上で動作します。JavaScriptや、LLVMフレームワークを介して多くのプラットフォームで実行可能なコードにもコンパイルできます。 また、Kotlin/Wasmは、Kotlinの新しいコンパイルターゲットであり、WebAssemblyをサポートする環境やデバイス上で実行可能なアプリケーションを作成することができます。これにより、Kotlinの強力な機能と柔軟性を活用して、Webアプリケーションやクライアントサイドのプログラミングにも取り組むことができます。
KotlinはJavaよりも簡潔で型安全性が高く、Scalaよりもシンプルな言語を目指して開発されました。Scalaと比較して、簡略化された結果、コンパイルが高速化され、IDEでの言語サポートも充実しています。また、KotlinはJavaと完全な互換性があり、Java開発者は徐々に導入することができます。特に、KotlinはAndroidにも組み込まれており、既存のAndroidアプリケーションは、アプリケーション全体を書き換えることなくKotlinで新機能を実装することができます。Kotlin FoundationがKotlin™商標を守っています。
はじめに
[編集]予備知識
[編集]Kotlinは多様なプラットフォームで利用できるモダンで柔軟なプログラミング言語です。
このチュートリアルに取り組む前に、以下の予備知識が役立ちます。
- プログラミング基礎
- 変数、制御構造(条件分岐、ループ)、関数など、プログラミングの基本的な概念を理解していることが望ましいです。これらの概念はKotlinを含む多くのプログラミング言語で共通しています。
- オブジェクト指向プログラミング(OOP)
- Kotlinはオブジェクト指向の概念を採用しており、クラス、オブジェクト、継承、ポリモーフィズムなどのOOPの基本的な理解が重要です。
- Javaの基本知識(Kotlin/JVMの場合)
- KotlinはJavaとの高い互換性を有しています。Javaの基本的な概念やJavaの標準ライブラリについての基本的な知識があると、Kotlin/JVMの理解がより深まります。
- Web開発基礎(Kotlin/JSの場合)
- Kotlin/JSはJavaScriptに変換され、Web開発に利用されます。HTMLやCSS、基本的なWeb開発の知識があると、Kotlin/JSを使用したWebアプリケーションの開発がスムーズに進みます。
- ネイティブ開発基礎(Kotlin/Nativeの場合)
- Kotlin/Nativeはネイティブな実行コードを生成し、組み込みシステムやiOSアプリケーションなどで利用されます。ネイティブ開発の基本的な知識があると、Kotlin/Nativeを活用した開発がしやすくなります。
以上の予備知識を持っていると、Kotlinをより効果的に学習し、異なるプラットフォームでの利用に対応することができます。各プラットフォームにおけるKotlinの特徴や最適な利用法を理解することで、幅広い開発環境で柔軟かつ効率的なプログラミングが可能になります。
プログラミング経験者へ
[編集]Kotlinを学ぶ際に他の言語のプログラミング経験がある場合、以下のアドバイスが役立つかもしれません。
- Java経験者へのアドバイス
- KotlinはJavaとの互換性が高いため、既存のJavaコードとの統合がスムーズに行えます。JavaのコードをKotlinに変換することも可能です。
- Kotlinの拡張関数やラムダ式など、新しい機能を積極的に試してみましょう。これにより、コードの簡潔性と可読性が向上します。
- 他の静的型付け言語経験者へのアドバイス
- Kotlinも静的型付け言語であり、型推論が強力です。しかし、null安全性や拡張関数など、他の言語にはない独自の機能にも焦点を当てて学習しましょう。
- Kotlinのコルーチンを利用して非同期処理を書くことで、他の言語での非同期コードよりもシンプルで効率的なコードを書けます。
- 関数型プログラミング経験者へのアドバイス
- Kotlinは関数型プログラミングの概念をサポートしています。ラムダ式や高階関数を駆使して、より宣言的で簡潔なコードを書く方法を探求してみてください。
- Kotlinの拡張関数やパターンマッチングを利用することで、関数型プログラミングの手法をさらに発展させることができます。
- Android開発者へのアドバイス
- KotlinはAndroidの公式開発言語として広く採用されています。既存のJavaベースのAndroidプロジェクトでもKotlinへの移行が可能です。Android Studioとの統合を利用して、Androidアプリケーションの開発にKotlinを組み込んでみましょう。
総じて、Kotlinは他のプログラミング言語において得た経験を活かしやすい言語です。適応期間は短く、生産性を高めるために積極的に新しい機能や手法を試してみることが重要です。また、公式のドキュメントやサンプルコードを利用して、Kotlinの言語仕様やベストプラクティスに精通することもおすすめです。
プログラミング未経験者へ
[編集]プログラミング未経験者がKotlinやプログラミング全般に取り組む際のアドバイスは以下の通りです:
- 基本的なプログラミング概念の理解
- プログラミングの基本概念(変数、条件分岐、ループなど)を理解することから始めましょう。これらの基本的な概念はほぼ全てのプログラミング言語で共通しています。
- Kotlinの特徴を理解
- KotlinがJavaとの互換性が高く、Androidアプリケーション開発で広く使われていることを知りましょう。Kotlinはコードの簡潔性や可読性を向上させるための多くの機能を提供しています。
- オンライン教材やチュートリアルを活用
- オンライン上には多くの無料または低コストでプログラミングを学べる教材やチュートリアルがあります。これらのリソースを活用して基礎から学び、実際に手を動かしながら進めてみましょう。
- 実践を重視
- 理論だけでなく、実際にコードを書いてみることが非常に重要です。小さなプログラムから始め、段階的に難易度を上げながら進めていくと良いでしょう。
- エラーを恐れず、デバッグの練習
- プログラムでエラーが発生することはよくあることです。エラーを見つけて修正することはプログラミングの一部であり、そのプロセスを恐れずに経験してみましょう。
- プロジェクトを通じた学習
- 実際のプロジェクトに取り組むことで、プログラミングの実践的な側面を理解しやすくなります。小さなプロジェクトから始め、徐々に複雑性を増していくと良いでしょう。
- コミュニティへの参加
- Kotlinやプログラミングに関するコミュニティやフォーラムに参加すると、他の学習者や経験者からのアドバイスやサポートを得ることができます。質問をしてコミュニケーションをとることも大切です。
- 継続的な学習と興味を持つ
プログラミングは絶え間ない学習が求められる分野です。新しい技術やツールに興味を持ち、継続的に学び続けることでスキルを向上させることができます。
プログラミングはスキルの向上に時間がかかるものですが、継続的な努力と実践を通じて着実に成長していきます。最初は小さなステップから始め、徐々に進んでいくことが大切です。
Hello world
[編集]他の多くのチュートリアルがそうであるように、 私たちもまずはKotlinの世界にあいさつすることから始めましょう。hello.ktというファイルを作り、次のように書いて保存して下さい(Kotlinのソースファイルの拡張子は .kt です)。
- hello.kt
fun main() = println("Hello, World!")
それでは、ソースプログラムをコンパイルして実行してみましょう。
% kotlinc hello.kt -include-runtime -d hello.jar % java -jar hello.jar Hello, World! % _
fun main()
:fun
は関数(function)を宣言するためのキーワードです。main
はプログラムのエントリーポイントとなる特別な関数の名前です。通常、プログラムの実行が始まるところです。
=
:- このイコール記号は、関数の本体が始まることを示します。Kotlinでは、単一の式の関数を記述する際に、本体とイコールを省略して1行で記述できます。
println("Hello, World!")
:println
はコンソールに文字列を表示するための関数です。("Hello, World!")
は引数として渡される文字列です。- この行全体は、関数の呼び出しとして、文字列 "Hello, World!" をコンソールに表示するものです。
これにより、この一行のコードは「main
という関数を宣言し、その中で 'Hello, World!' という文字列をコンソールに表示する」という単純なプログラムを実現しています。Kotlinはこのようにシンプルかつ読みやすい構文を持っており、初学者にも優れた学習言語となっています。
クイックツアー
[編集]Kotlinのクイックツアーでは、基本的な構文や機能に焦点を当て、簡単なコード例を通じて言語の特徴を理解していきます。
- 変数と基本的なデータ型
fun main() { // 変数の宣言 val message: String = "Hello, Kotlin!" // 自動型推論も可能 val number = 42 // 文字列テンプレート println("$message The answer is $number") }
- 条件分岐とループ
fun main() { val score = 75 // 条件分岐 if (score >= 80) { println("Great job!") } else if (score >= 60) { println("Good job!") } else { println("You can do better.") } // ループ for (i in 1..5) { println("Count: $i") } }
- 関数の定義と呼び出し
fun main() { // 関数の呼び出し greet("Alice") // 関数の定義 fun greet(name: String) { println("Hello, $name!") } }
- クラスとオブジェクト
fun main() { // クラスの定義 class Person(val name: String, val age: Int) // オブジェクトの作成 val person = Person("John", 25) // プロパティへのアクセス println("${person.name} is ${person.age} years old.") }
- Null安全性
fun main() { // Null許容型 var name: String? = "Bob" // 安全呼び出し演算子 val length: Int? = name?.length // Elvis演算子 val result = name ?: "Guest" println("Name length: $length") println("Result: $result") }
- 拡張関数
fun main() { // 拡張関数の定義 fun String.addExclamation(): String { return "$this!" } // 拡張関数の呼び出し val greeting = "Hello".addExclamation() println(greeting) }
- コルーチン
import kotlinx.coroutines.* fun main() { // コルーチンの起動 GlobalScope.launch { delay(1000) // 1秒待つ println("World!") } println("Hello, ") // プログラムを待機して終了しないようにする Thread.sleep(2000) // 2秒待つ }
- Kotlin DSL
data class Person(val name: String, val age: Int) fun buildPerson(block: PersonBuilder.() -> Unit): Person { val builder = PersonBuilder() builder.block() return builder.build() } class PersonBuilder { var name: String = "" var age: Int = 0 fun build(): Person { return Person(name, age) } } fun main() { val person = buildPerson { name = "Alice" age = 30 } println("Built person: $person") // => Built person: Person(name=Alice, age=30) }
- この例では、Kotlin DSLを使用して、特定のドメイン(ここでは
Person
オブジェクトの構築)に特化した言語拡張を行っています。
- Smart cast
- Kotlinは型の安全性を高めるためにスマートキャストを提供しています。これにより、条件分岐やインスタンスのチェック後に自動的にキャストが行われ、冗長なキャストを省略できます。
fun processValue(value: Any) { if (value is String) { // valueはString型として扱われる println(value.length) // ここでlengthプロパティにアクセスできる } if (value is Int) { // valueはInt型として扱われる println(value * 2) // ここでvalueをIntとして使用できる } // スマートキャストを利用することで、valueがどの型であるかを明示的にキャストする必要がない } fun main() { processValue("Hello") // 出力: 5 (Stringの長さ) processValue(10) // 出力: 20 (Intを2倍にした値) }
これらの例はKotlinの基本を紹介するものであり、より高度な概念や機能も学ぶことができます。
インストール方法
[編集]Kotlinは、ターゲットごとに
- Kotlin/JVM
- Java仮想マシン(JVM)で実行可能なコードを生成。
- Kotlin/JS
- JavaScriptのコードを生成。
- Kotlin/Native
- バックエンドにLLVMインフラストラクチャー を利用してネイティブコードコードを生成。
の3つの実装があり、ツールチェインとしては統合されていますが、使用するコマンドやオプションが異なります。
Kotlin/JVM 環境のインストール
[編集]Kotlin/JVM 環境をインストールする手順は以下の通りです:
- Java JDK のインストール:
- Kotlin は JVM 上で動作するため、まずは Java Development Kit (JDK) をインストールする必要があります。
- Oracle JDK や OpenJDK のいずれかを選択してインストールしてください。
- JDK 11 以上を推奨します。
- ⇒ Java/プログラミングのための準備
- Kotlin Compiler のダウンロード:
- Kotlin コンパイラをダウンロードしてインストールします。
- 公式の Kotlin ダウンロードページ ( https://kotlinlang.org/docs/command-line.html ) から最新のバージョンを入手してください。
- パスの設定: Kotlin コンパイラのパスをシステムの環境変数 PATH に追加します。これにより、コマンドラインから Kotlin を直接実行できるようになります。
- 簡単なプロジェクトの作成と実行:
- テキストエディタを使って Kotlin コードを作成し、コンパイル・実行してみてください。例えば、以下の Hello World プログラムを作成し、
hello.kt
という名前で保存します。- hello.kt
fun main() = println("Hello, Kotlin!")
- このファイルをコンパイルして実行するには、ターミナルまたはコマンドプロンプトで次のコマンドを実行します。
kotlinc hello.kt -include-runtime -d hello.jar java -jar hello.jar
- これにより、"Hello, Kotlin!" というメッセージが出力されます。
- テキストエディタを使って Kotlin コードを作成し、コンパイル・実行してみてください。例えば、以下の Hello World プログラムを作成し、
以上で、Kotlin/JVM 環境のインストールが完了します。
Kotlin/JS 環境のインストール
[編集]Kotlin/Native 環境のインストール
[編集]実行方法
[編集]Kotlin/JVM
[編集]Kotlin/JVM では、Kotlin のソースファイルからJARファイルをコンパイルします。 Kotlin のソースファイルの拡張子は .kt です。
hello.kt
をコンパイルして hello.jar
を得るのであれば
- コンパイル
kotlinc hello.kt -include-runtime -d hello.jar
- とします。
生成された hello.jar を実行するには
- kotlinから生成したJARファイルの実行
java -jar hello.jar
- とします。
- -jar を忘れると
$ java hello.jar Error: Could not find or load main class hello.jar Caused by: java.lang.ClassNotFoundException: hello.jar
- とエラーになります
別のKotlinのソースファイル universe.kt
をコンパイル/実行するには hello
を filename
に読替えて同じ手順を行えばいいのですが、このような単純作業はコンピューターに任せましょう。
make の利用
[編集]ビルド手順の自動化を行うツールに make があります。 make にはいくつかの方言がありますが、BSD-make と GNU-make に共通した構文を紹介します。
- Makefile
# Makefile for Kotlin .SUFFIXES: .kt .jar .run .kt.jar: kotlinc $^ -include-runtime -d $@ .jar.run: java -jar $^ all:
- この内容をカレントディレクトリーに Makefile の名前で保存します(カレントディレクトリーに hello.kt がある場合)。
- タブと空白には区別され、先頭の空白8つ分はタブです。
コマンドラインでの操作は
- tcshの場合
% cat hello.kt fun main() { println("Hello world!") } % make hello.jar kotlinc hello.kt -include-runtime -d hello.jar % make hello.run java -jar hello.jar Hello world! % sed -e s/world/universe/ hello.kt > universe.kt % cat universe.kt fun main() { println("Hello universe!") } % make universe.run kotlinc universe.kt -include-runtime -d universe.jar java -jar universe.jar Hello universe! rm universe.jar % make universe.jar kotlinc universe.kt -include-runtime -d universe.jar % make universe.run java -jar universe.jar Hello universe!
make hello.jar
とすると- ルール
.kt.jar: kotlinc $^ -include-runtime -d $@
- が適用され
- アクション
kotlinc hello.kt -include-runtime -d hello.jar
- が実行されます。
make hello.run
とすると- ルール
.jar.run: java -jar $^
- が適用され
- アクション
java -jar hello.jar
- が実行されます。
- universe.jar がない状態で
make universe.run
とすると。- ルール
.kt.jar: kotlinc $^ -include-runtime -d $@ .jar.run: java -jar $^
- が連鎖的に適用され
- アクション
java -jar universe.jar Hello universe! rm universe.jar
- となります。
- もともと universe.jar はなかったので最後に rm universe.jar して消しています。
この様に、make を使うとファイルのタイムスタンプから必要な処理を判断し実行します。
make と同じくビルドツールに gradle があり、gradle のビルドルールは Kotlin Script で書けるので、Kolin の学習には gradle が適しているとも言えますが、ビルドルールを書くためにKotlinのコードを読み書きする必要があるという「鶏卵問題」に陥るので、より一般的な make を紹介しました。
ここでは、kotlinソースからJARファイルをコンパイルし実行する最小限のルールを書きましたが、機会をみて、make のチュートリアルを書こうと思います。
特徴
[編集]- コンパイル型言語
- Kotlinはコンパイラーとして実装されています。REPLやスクリプティングエンジンもあります。
- Kotlin/JVM
- 1つまたは複数のソースコードをコンパイルしてJavaバイトコードを生成し、生成したJavaバイトコードを実行します。
- Kotlin/JS
- 1つまたは複数のソースコードをコンパイルしてJavaScriptを生成し、生成したJavaScriptを実行します。
- Kotlin/Native
- 1つまたは複数のソースコードをコンパイルしてLLVMインフラストラクチャーをバックエンドに実行形式を生成し、生成した実行形式を実行します。
- Kotlin/Wasm
- 1つまたは複数のソースコードをコンパイルしてWebAssembly(Wasm)バイナリを生成し、WebAssembly仮想マシン上で実行します。
- 静的型付け
- 値・変数・関数のパラメーター・関数の戻値などの型はコンパイル時に検証され、型安全性が担保されます。
- 例外
- try-catch-finally 形の例外処理をサポートします。
- 演算子オーバーロード
- サポートします。
a + b
は、メソッド形式a.plus(b)
と同義です。 - 関数オーバーロード
- 同じ名前で引数の数が異なる関数を定義することが可能です。
- ラムダ式
- サポートします。
{ a: Int, b: Int -> a + b }
- 無名関数
- サポートします。
fun(a: Int, b: int) = a + b
- 拡張関数
- 既存のクラスに新しいメソッドを生やすことができます。これは Java のクラスライブラリーのクラスも例外ではありません。
- 宣言や定義にはキーワードを伴う
- 関数定義なら
fun
、定数宣言ならval
、変数宣言ならvar
、クラス定義ならclass
など、明示的にキーワードを使い宣言するので grep フレンドリーです。また、列挙型はenum
ではなく、enum class
で列挙だと知らなくても class で検索すれば見落としません。C/C++は、int f(int ch){...}, int i = 9;
のように、よく読まないと関数定義なのか変数宣言かわからないのとは対照的です。特にC++は、int a(3)
が関数定義なのか変数宣言なのかに曖昧さがあり、パースの時点では確定せずコンパイラーもプログラマーも悩まされます。 - 型推論
- サポートします。
- ガベージコレクション
- サポートします。
- クラス
- クラスベースのオブジェクト指向言語です。
- 全てがオブジェクト
- Javaで言うプリミティブもオブジェクトで、メソッドを持ちます。
- 祖先クラス
- Any
- コンストラクター
- プライマリーコンストラクター・セカンダリコンストラクター・init の3つのメソッドがインスタンス化の機能が割振られます。
- デストラクター
- ありません。
- 継承
- 単一継承をサポートします。
- 抽象クラス
- Java や Go の interface や Swift の protocol はありませんが、abstract class があります。
コード・ギャラリー
[編集]エラトステネスの篩
[編集]エラトステネスの篩を、若干 Kotlin らしく書いてみました。
- エラトステネスの篩
fun eratosthenes(n: Int) { var sieve = BooleanArray(n + 1){ it >= 2 } for (i in 2..<sieve.size) { if (!sieve[i]) continue; for (j in i * i until sieve.size step i) sieve[j] = false if (i * i >= sieve.size) break; } for (i in 2..<sieve.size) { if (!sieve[i]) continue; print("$i ") } } fun main() { eratosthenes(100); }
- 実行結果
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
- 使用頻度の高い sieve は、実行効率の良いプリミティブ型配列の BooleanArray として、初期化式を it >= 2 として宣言と同時に素数候補をマークしました。
- i のループはオーソドックスな IntRange をコレクションにした for ですが、j のループは少し変則的で until は制御構造に見えますが、メソッド
infix Int.until(to: Int) : IntProgression
で、infix 修飾による中置構文です。
最大公約数と最小公倍数
[編集]最大公約数と最小公倍数を、若干 Kotlin らしく書いてみました。
- 最大公約数と最小公倍数
tailrec fun gcd2(a: Int, b: Int): Int = if (b == 0) a else gcd2(b, a % b) fun gcd(vararg numbers: Int): Int = numbers.reduce(::gcd2) fun lcm2(a: Int, b: Int): Int = a * b / gcd2(a, b) fun lcm(vararg numbers: Int): Int = numbers.reduce(::lcm2) fun main() { println("gcd2(30, 45) => ${gcd2(30, 45)}") println("gcd(30, 72, 12) => ${gcd(30, 72, 12)}") println("lcm2(30, 72) => ${lcm2(30, 72)}") println("lcm(30,42,72) => ${lcm(30,42,72)}") }
- 実行結果
gcd2(30, 45) => 15 gcd(30, 72, 12) => 6 lcm2(30, 72) => 360 lcm(30,42,72) => 2520
- 関数 gcd2() は、2つの整数の最大公約数をユークリッドの互除法で求めています。
- gcd2() の修飾子
tailrec
は、コンパイラーに末尾再帰が行なわれていることを教え、再帰をループにするヒントを与えています。
- gcd2() の修飾子
- 関数 gcd() は可変引数関数で、全ての引数に gcd2() を適用し最大公約数を返します。
- 関数 lcm2() は、2つの整数の最小公倍数を、を利用して求めています。
- 関数 lcm() は可変引数関数で、全ての引数に lcm2 を適用し最小公倍数を返します。
それぞれの関数定義は式形式で1行なので拍子抜けですが、概念を簡素にかけていると思います。
二分法
[編集]二分法を、若干 Kotlin らしく書いてみました。
- 二分法
import kotlin.math.abs tailrec fun bisection(low_: Number, high_: Number, f: (Double) -> Double) : Double { var low = low_.toDouble() var high = high_.toDouble() val x = (low + high) / 2; val fx = f(x); if (abs(fx) < +1.0e-10) return x; if (fx < 0.0) low = x; else high = x; return bisection(low, high, f); } fun main() { println(bisection(0, 3){x : Double -> x - 1}) println(bisection(0, 3){x : Double -> x * x - 1}) }
- 実行結果
0.9999999999417923 1.0000000000291038
- 旧課程(-2012年度)高等学校数学B/数値計算とコンピューター#2分法の例を Kotlin に移植しました。
- 命題の式をラムダで渡すのが肝ですが、Kotrinは関数の最後パラメーターが関数型の場合括弧の外に追い出せるので
bisection(0, 3){x : Double -> x - 1}
のような表現ができます。
- 引数で受取った範囲を一旦varに移替えているのは、関数の引数がイミュータブルなためです。
- また、どうせローカル変数に移すならパラメータは Number 型としてIntなども受け入れるようにし、.toDouble()で型を揃えています。
- このコードはまた、
tailrec
の好例にもなっています。
複素数型
[編集]オーソドックスな複素数型の実装(Kotlinの標準ライブラリーには複素数型が見当たらなかったので)
- Complex.kt
import kotlin.math.abs import kotlin.math.atan2 import kotlin.math.hypot import kotlin.math.sin import kotlin.math.cos import kotlin.math.tan import kotlin.math.sinh import kotlin.math.cosh import kotlin.math.tanh import kotlin.math.PI class Complex(real_: Double, imag_: Double) : Number() { private val pair = doubleArrayOf(real_, imag_) val real get() = pair[0] val imag get() = pair[1] constructor(real_: Number, imag_: Number) : this(real_.toDouble(), imag_.toDouble()) { // println("($real_, ${imag_}.i) => (${this.real}, ${this.imag}.i)") } companion object { public val i = Complex(0, 1) public val NaN = Complex(Double.NaN, Double.NaN) public val INFINITY = Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY) } fun isNaN() = real.isNaN() or imag.isNaN() fun isFinite() = real.isFinite() and imag.isFinite() fun imagAsZero() = abs(imag) < 1e-12 fun check() = when { this.isNaN() -> throw Exception("NaN!") !this.imagAsZero() -> throw Exception("imag($imag) != 0") else -> true } override fun toChar() = toInt().toChar() override fun toByte() = toInt().toByte() override fun toShort() = toInt().toShort() override fun toInt() = if (check()) real.toInt() else 0 override fun toLong() = if (check()) real.toLong() else 0L override fun toFloat() = if (check()) real.toFloat() else 0.0F override fun toDouble() = if (check()) real else 0.0 override fun toString() = "${real.toString()}+${imag.toString()}.i".replace("+-","-") val abs get() = hypot(real, imag) val arg get() = when { isNaN() -> Double.NaN !isFinite() -> Double.NaN real > 0.0 -> atan2(imag, real) real < 0.0 && imag >= 0.0 -> atan2(imag, real) + PI real < 0.0 && imag < 0.0 -> atan2(imag, real) - PI real == 0.0 && imag > 0.0 -> +PI / 2 real == 0.0 && imag < 0.0 -> -PI / 2 else -> 0.0 // indeterminate } val angle get() = arg val phase get() = arg val conjugate get() = Complex(+real, -imag) val conj get() = conjugate operator fun plus(r: Number) = Complex(real + r.toDouble(), imag) operator fun plus(r: Complex) = Complex(real + r.real, imag + r.imag) operator fun minus(r: Number) = Complex(real - r.toDouble(), imag) operator fun minus(r: Complex) = Complex(real - r.real, imag - r.imag) operator fun times(r: Number) = Complex(real * r.toDouble(), imag * r.toDouble()) // (a+bi)(c+di) = a c + bi c + a di + bi di = ac - b d + (b c + a d)i operator fun times(r: Complex) = Complex(real * r.real - imag * r.imag, imag * r.real + real * r.imag) operator fun div(r: Number) = Complex(real / r.toDouble(), imag / r.toDouble()) // this / r = this * r.conjugate / r * r.conjugate operator fun div(r: Complex) = this * r.conjugate / (r.real * r.real + r.imag * r.imag) operator fun unaryMinus() = Complex(-real, -imag) override fun equals(other: Any?) = when { this === other -> true other !is Complex -> false real != other.real -> false else -> imag == other.imag } fun sin() : Complex { val c = Complex(-imag, real).sinh(); return Complex(c.imag, -c.real) } fun sinh() = sinh(real) * cos(imag) + (cosh(real) * sin(imag)).i } operator fun Number.plus(c:Complex) = Complex(this.toDouble(), 0.0) + c operator fun Number.minus(c:Complex) = Complex(this.toDouble(), 0.0) - c operator fun Number.times(c:Complex) = Complex(this.toDouble(), 0.0) * c operator fun Number.div(c:Complex) = Complex(this.toDouble(), 0.0) / c val Number.r get() = Complex(toDouble(), 0.0) val Number.i get() = Complex(0.0, toDouble()) fun main() { val a = 2.1 + 3.9.i val b = -7.8 + 11.11.i val c = 3 + 4.i val p = 2.3.r val q = 1.3.i try { Complex.NaN.toDouble() } catch (e: Exception) { println("Complex.NaN.toDouble() raises $e") } try { Complex.INFINITY.toDouble() } catch (e: Exception) { println("Complex.INFINITY.toDouble() raises $e")} try { Double.POSITIVE_INFINITY.r.toDouble() } catch (e: Exception) { println("Double.POSITIVE_INFINITY.r.toDouble() raises $e")} try { 1.i.toDouble() } catch (e: Exception) { println("1.i.toDouble() raises $e")} println( """ a == a => ${a == a} a != a => ${a != a} a == 2.1 + 3.9.i => ${a == 2.1 + 3.9.i} 3.14.i == 3.14.i => ${3.14.i == 3.14.i} Double.NaN.i == Double.NaN.i => ${Double.NaN.i == Double.NaN.i} Double.POSITIVE_INFINITY.r == Double.POSITIVE_INFINITY.r + 1 => ${Double.POSITIVE_INFINITY.r == Double.POSITIVE_INFINITY.r + 1} Complex(0, 1).arg => ${Complex(0.0, 1).arg} Complex(0, -1).arg => ${Complex(0.0, -1).arg} Complex(1, 1).arg => ${Complex(1.0, 1).arg} Complex(1, -1).arg => ${Complex(1.0, -1).arg} Complex(1, 0.0).arg => ${Complex(1.0, 0.0).arg} Complex(-1, 0.0).arg => ${Complex(-1.0, 0.0).arg} Complex(0.0, 0.0).arg => ${Complex(0.0, 0.0).arg} 2.toBigDecimal() = ${2.toBigDecimal()} 2.toBigDecimal().i = ${2.toBigDecimal().i} a => $a b => $b c => $c p => $p q => $q Double.NaN.r => ${Double.NaN.r} Double.NaN.i => ${Double.NaN.i} Double.POSITIVE_INFINITY.r => ${Double.POSITIVE_INFINITY.r} Double.POSITIVE_INFINITY.i => ${Double.POSITIVE_INFINITY.i} -Double.POSITIVE_INFINITY.r => ${-Double.POSITIVE_INFINITY.r} Double.POSITIVE_INFINITY.i.conj => ${Double.POSITIVE_INFINITY.i.conj} (1+2.i) / 0 => ${(1+2.i) / 0} (0+0.i) / 0 => ${(0+0.i) / 0.0} Double.NaN.i.isNaN() => ${Double.NaN.i.isNaN()} Double.NaN.i.isFinite() => ${Double.NaN.i.isFinite()} 1.i.isNaN() => ${1.i.isNaN()} 1.i.isFinite() => ${1.i.isFinite()} Complex.INFINITY.isNaN() => ${Complex.INFINITY.isNaN()} Complex.INFINITY.isFinite() => ${Complex.INFINITY.isFinite()} -a => ${-a} a + 8 => ${a + 8} 8 + a => ${8 + a} a + b => ${a + b} a - b => ${a - b} a * b => ${a * b} a * Complex(1, 1) => ${a * Complex(1, 1)} a / 2 => ${a / 2} a / b => ${a / b} a / b * b => ${a / b * b} a * 1.i => ${a * 1.i} a / 1.i => ${a / 1.i} 1.i => ${1.i} 1.i / 1.i => ${1.i / 1.i} 1.0 + 3.2.i => ${1.0 + 3.2.i} a + 3.2 => ${a + 3.2} a.conjugate => ${a.conjugate} 1 + 2.i => ${1 + 2.i} """ ) }
- 実行結果
Complex.NaN.toDouble() raises java.lang.Exception: NaN! Complex.INFINITY.toDouble() raises java.lang.Exception: imag(Infinity) != 0 1.i.toDouble() raises java.lang.Exception: imag(1.0) != 0 a == a => true a != a => false a == 2.1 + 3.9.i => true 3.14.i == 3.14.i => true Double.NaN.i == Double.NaN.i => false Double.POSITIVE_INFINITY.r == Double.POSITIVE_INFINITY.r + 1 => true Complex(0, 1).arg => 1.5707963267948966 Complex(0, -1).arg => -1.5707963267948966 Complex(1, 1).arg => 0.7853981633974483 Complex(1, -1).arg => -0.7853981633974483 Complex(1, 0.0).arg => 0.0 Complex(-1, 0.0).arg => 6.283185307179586 Complex(0.0, 0.0).arg => 0.0 2.toBigDecimal() = 2 2.toBigDecimal().i = 0.0+2.0.i a => 2.1+3.9.i b => -7.8+11.11.i c => 3.0+4.0.i p => 2.3+0.0.i q => 0.0+1.3.i Double.NaN.r => NaN+0.0.i Double.NaN.i => 0.0+NaN.i Double.POSITIVE_INFINITY.r => Infinity+0.0.i Double.POSITIVE_INFINITY.i => 0.0+Infinity.i -Double.POSITIVE_INFINITY.r => -Infinity-0.0.i Double.POSITIVE_INFINITY.i.conj => 0.0-Infinity.i (1+2.i) / 0 => Infinity+Infinity.i (0+0.i) / 0 => NaN+NaN.i Double.NaN.i.isNaN() => true Double.NaN.i.isFinite() => false 1.i.isNaN() => false 1.i.isFinite() => true Complex.INFINITY.isNaN() => false Complex.INFINITY.isFinite() => false -a => -2.1-3.9.i a + 8 => 10.1+3.9.i 8 + a => 10.1+3.9.i a + b => -5.699999999999999+15.01.i a - b => 9.9-7.209999999999999.i a * b => -59.70899999999999-7.088999999999999.i a * Complex(1, 1) => -1.7999999999999998+6.0.i a / 2 => 1.05+1.95.i a / b => 0.14624568776282462-0.2916936421737203.i a / b * b => 2.1000000000000005+3.8999999999999995.i a * 1.i => -3.9+2.1.i a / 1.i => 3.9-2.1.i 1.i => 0.0+1.0.i 1.i / 1.i => 1.0+0.0.i 1.0 + 3.2.i => 1.0+3.2.i a + 3.2 => 5.300000000000001+3.9.i a.conjugate => 2.1-3.9.i 1 + 2.i => 1.0+2.0.i
クラス定義とインスタンス化とメンバー関数
[編集]- シンプルなクラス定義
class Hello(val s : String = "world") { override fun toString() = "Hello $s!" fun print() = println(s) } fun main() { val hello1 = Hello() println(hello1) hello1.print() val hello2 = Hello("my friend") println(hello2); hello2.print() print( """ Hello::class.simpleName => ${Hello::class.simpleName} hello1 => $hello1 hello2.s => ${hello2.s} """ ) }
- 実行結果
Hello world! world Hello my friend! my friend Hello::class.simpleName => Hello hello1 => Hello world! hello2.s => my friend
- Ruby#クラスの例を、Kotlin に移植しました。
- 冒頭4行がクラス定義です。
- クラス定義に、他のオブジェクト指向言語ならコンストラクタに渡すような引数が渡されています。
- メンバーを公開するケースなら、この様に宣言的な引数リストを使うとメンバー定義と暗黙の初期値を与えられます。
- toString は、オブジェクトを文字列化するメンバー関数で、Anyの同名のメンバー関数をオーバーライドしています。
- print は、このクラスに独自なメンバー関数で、println() の戻値型(= Unit)を戻値型としています。
ファイルの読出し
[編集]- JavaのI/Oシステムを使いファイルを読込む
import java.nio.file.Files import java.nio.file.Paths import java.nio.charset.Charset import java.io.IOException fun main() { try { for (s in Files.readAllLines(Paths.get("/etc/hosts"), Charset.forName("UTF-8"))) { println(s) } } catch (e: IOException) { e.printStackTrace() } }
- readText版
import java.io.File fun main(args: Array<String>) { print(File("/etc/hosts").readText()) }
チートシート
[編集]- エントリーポイント
-
- コマンドライン引数を受取らない場合
fun main() { // ... }
- コマンドライン引数を受取る場合
fun main(args: Array<String>) { println(args.contentToString()) }
- コメント
// Kotlinには、行末で終わるコメントと fun main() { /* * 複数行に渡ることと * /* * 入れ子にすることができる * コメントがあります */ */ println("Hello, World!") }
- プリミティブデータ型
データ型 サイズ
(ビット)初期値 Boolean 1 false Byte 8 0 Short 16 0 Int 32 0 Long 64 0L Float 32 0.0f Double 64 0.0
- リテラル
-
- 整数リテラル
123, 0b11010, 0o177, 0xbadbeef
- 浮動小数点数リテラル
3.14, 1.2e-9
- 文字リテラル
'a', '漢', '¥t'
- 文字列リテラル
”abc”, "了解🏝👅"
- 複数行に渡る文字列リテラル
""" 複数行に渡る場合は この様に 3つの"(ダブルクオーテーションマーク)で囲むことで記述できます。 """
- 配列の生成
arrayOf(2,3,5,7), Array(5){1+it)
- 制御構造
-
- 分岐
-
- if
-
- else節のないif
if ( 条件式 ) 式
- else節のないifは、値を取ろうとするとコンパイルエラーになります。
- ifの値
if ( 条件式 ) 式1 else 式2
- 条件式が false でなければifの値は 式1
- false ならば 式2
- when
-
- 式で分岐
when ( 式 ) { 値0 -> 式0 値1, 値2... , 値n -> 式1 in 範囲式 -> 式2 !in 範囲式 -> 式3 is 型 -> 式4 !is 型 -> 式5 elase -> 式x }
- 式を省略すると true が仮定されます
when { 式0 -> 式0x 式1, 式2... , 式n -> 式1x elase -> 式x }
- 値を返すwhen(による再帰)
fun power(n: Int, i: Int) = when { i < 0 -> throw Exception("Negative powers of integers cannot be obtained.") i == 0 -> 1 i == 1 -> n else -> n * power(n, i - 1) }
- コレクション
-
- リストの生成
- マップの生成
- リストへのアクセス
- マップへのアクセス
- フィルタリング
- マップへの変換
- クラス
-
- enum class
- abstract class
- 関数
-
- 関数呼出し
- 関数定義
KotlinとJavaの比較
[編集]KotlinとJavaは、どちらもJava Virtual Machine(JVM)上で動作するプログラミング言語です。以下は、KotlinとJavaの主な比較ポイントです。
- Null Safety(null安全性)
- Kotlin
- Kotlinはnull安全性を重視しており、デフォルトで変数がnullになる可能性がある場合、その変数をnullableとして扱います。これにより、nullによる実行時のエラーを減少させます。
- Java
- 近年のJavaバージョンでは、
Optional
やNullable
などの概念が導入され、nullの扱いが改善されました。しかし、Kotlinのnull安全性はより厳格であり、コンパイル時の警告やエラーが発生しやすくなっています。
- コードの簡潔性
- Kotlin
- KotlinはJavaに比べてコードが簡潔で読みやすくなっています。これは、Kotlinがnullチェックや型推論などを自動的に処理し、冗長なコードを排除できるからです。
- Java
- Javaも近年ではラムダ式やストリームAPIなど、コードの簡潔性を向上させる機能が導入されていますが、依然として冗長なコードが残ることがあります。
- 関数型プログラミング
- Kotlin
- Kotlinは関数型プログラミングをサポートしており、ラムダ式や高階関数の使用が簡単です。
- Java
- Javaも関数型プログラミングをサポートしていますが、Kotlinの方がよりシンプルになっています。
- 互換性
- Kotlin
- KotlinはJavaのライブラリやフレームワークを使用することができ、Javaとの相互運用性が高いです。また、JavaからKotlinに変換することも可能です。
- Java
- JavaはKotlinのコードを直接使用することはできませんが、既存のJavaコードとの連携がスムーズです。
- パフォーマンス
- Kotlin
- KotlinはJavaに比べて少し遅いとされていますが、実際のアプリケーションではその違いがほとんど実感できないことが多いです。KotlinはJavaのバイトコードに変換されるため、ほとんど同じパフォーマンスを実現できます。
- ツールのサポート
- Kotlin
- KotlinはJavaと同じツールを使用できるため、Javaエコシステムのメリットを享受しながら、より現代的な言語機能を利用できます。
総合的に見ると、KotlinはJavaよりも簡潔で読みやすく、より現代的な言語機能を備えています。ただし、Javaのライブラリやフレームワークを使用する場合は、Javaを使用することが必要な場合があります。
KotlinとScalaの比較
[編集]KotlinとScalaは、どちらもJVM(Java Virtual Machine)上で動作する静的型付け言語であり、それぞれ異なる特徴を持っています。以下に、KotlinとScalaをいくつかの側面で比較してみます。
- パフォーマンス
- Kotlin
- Kotlinは、Javaと同等のパフォーマンスを提供し、メモリ使用量が比較的少ないです。Androidアプリケーションの開発においては特に優れた性能を発揮します。
- Scala
- Scalaは一般的にはJavaよりも高速であるとされていますが、同時に多くのメモリを使用します。複雑なシステムの開発において、高いパフォーマンスが求められる場面で利用されます。
- 文法
- Kotlin
- Kotlinの文法はJavaに似ており、より簡素であり、Javaからの移行が容易です。これにより、Java開発者にとっては理解しやすいものです。
- Scala
- Scalaの文法は高度であり、関数型プログラミングの概念を強力にサポートしています。初めてのユーザーにとっては学習コストが高いかもしれませんが、高度な機能を提供しています。
- 開発用途
- Kotlin
- Kotlinは主にAndroidアプリケーションの開発に最適化されています。Android Studioでのサポートが強力であり、Androidエコシステムとの統合がスムーズです。
- Scala
- Scalaは複雑なシステムや大規模なプロジェクトの開発に適しています。特に関数型プログラミングの特徴を生かした開発に向いています。
- 可読性
- Kotlin
- KotlinはJavaと同じように、シンプルで読みやすいコードを書くことができます。文法がシンプルであるため、可読性が高いです。
- Scala
- Scalaは高度な機能を提供するが故に、可読性が低下する可能性があります。特に初心者にとっては、学習コストが高いと感じることがあります。
- 拡張性
- Kotlin
- Kotlinはよりシンプルでクラスベースのプログラミングスタイルを提供しており、拡張性があります。拡張関数や拡張プロパティなどが利用できます。
- Scala
- Scalaは関数型プログラミングの特徴を強調しており、高度な拡張性を提供しています。パターンマッチングや型クラスなどが利用できます。
総合的に見ると、KotlinはJavaからの移行が容易であり、Androidアプリケーションの開発に特に適しています。一方で、Scalaは関数型プログラミングの特徴を生かして複雑なシステムの開発に向いており、高度な拡張性を提供しています。選択する言語は、開発の目的や開発者のスキルセットによって異なることがあります。
KotlinとSwiftの比較
[編集]KotlinとSwiftは、それぞれAndroidとiOSのアプリ開発で主に使用されるプログラミング言語です。以下に、両言語の類似点と相違点をいくつか挙げてみます。
- 類似点
-
- 静的型付け
- KotlinとSwiftは両方とも静的型付け言語であり、コンパイル時に型の確認が行われます。これにより、実行時エラーを事前に把握しやすくなり、安全性が向上します。
- モダンな構文
- どちらの言語もモダンで読みやすい構文を採用しており、コードの記述が簡潔になっています。これにより、開発者は効率的かつ迅速にコードを書くことができます。
- ヌル安全性
- KotlinとSwiftはヌル安全性の考え方を取り入れており、ヌルポインターによるエラーを防ぐための手段を提供しています。これにより、安定したアプリケーションの開発が可能です。
- 相違点
-
- ターゲットプラットフォーム
- Kotlinは主にAndroidアプリ開発に焦点を当てていますが、JetBrainsによって開発・サポートされているため、マルチプラットフォーム開発にも利用されています。
- SwiftはAppleが開発し、iOS、macOS、watchOS、tvOSなどのApple製品向けのアプリ開発に特化しています。
- 開発元とサポート
- KotlinはJetBrainsによって開発・サポートされています。JetBrainsはプロの開発者向けのツールを提供しており、Kotlinの統合も図られています。
- SwiftはAppleによって開発され、iOSや関連プラットフォーム向けに積極的にサポートされています。Xcodeなどの開発ツールも提供されています。
- 言語機能の違い
- KotlinはJavaとの相互運用性が高く、既存のJavaコードとの統合が容易です。これにより、Android開発においても既存のJavaライブラリを利用することができます。
- SwiftはObjective-Cとの相互運用性があり、iOS開発においてはObjective-Cコードとの統合が可能です。SwiftはObjective-Cよりもシンプルであり、関数型プログラミングの機能も強化されています。
- 構文と特徴
- KotlinはJavaに似た構文を持ちつつ、いくつかの新しい機能や改善を提供しています。Javaからの移行が容易であり、Androidアプリケーション開発に適しています。
- SwiftはObjective-Cよりもシンプルで読みやすい構文を持っており、関数型プログラミングの機能が強化されています。iOSアプリケーション開発において使用されます。
どちらの言語も優れた特性を持っており、選択はプロジェクトのニーズや開発者の好みによって異なります。
エントリーポイント
[編集]Kotlinでは、関数 mainがエントリーポイントです。
- noop.kt
fun main() {}
なにもしないプログラムはこの様になります。
コマンドライン引数を受取る場合
[編集]- use-args.kt
fun main(args: Array<String>) { println(args.contentToString()) }
- Shell
% kotlinc use-args.kt -include-runtime -d use-args.jar % java -jar use-args.jar 123 abc 漢字 [123, abc, 漢字] % _
パッケージ
[編集]パッケージの指定は、ソースファイルの冒頭(shebang!の次)で行ってください[1]。
package my.demo import kotlin.text.* // Imprementation
ディレクトリとパッケージの一致は必須ではなく、ソースファイルはファイルシステム上に任意に配置することができます。
ディフォルトインポート
[編集]Kotlinでは、特定のパッケージやクラスを明示的にインポートすることなく、いくつかのデフォルトのインポートが提供されています。これにより、コードをより簡潔にし、冗長性を減少させることができます。以下は、デフォルトでインポートされる主要なパッケージです。
- kotlin.*
- Kotlinの基本的な機能や標準ライブラリが含まれています。これにより、Kotlinの核となる機能を利用するための基本的な道具が提供されます。
- kotlin.annotation.*
- アノテーション関連の機能が含まれています。アノテーションは、プログラムにメタデータを追加するための重要な手段です。
- kotlin.collections.*
- コレクション関連のクラスや関数が含まれています。これにより、リスト、セット、マップなどのデータ構造を効果的に扱うための機能が提供されます。
- kotlin.comparisons.*
- 比較関連の機能が含まれています。ソートや比較などの操作をより容易に行うことができます。
- kotlin.io.*
- 入出力関連の機能が含まれています。例えば、ファイルの読み書きや標準出力への出力などが簡単に行えます。
- kotlin.ranges.*
- 範囲関連の機能が含まれています。これを使用することで、特定の範囲内の要素を操作する際に便利なメソッドが提供されます。
- kotlin.sequences.*
- シーケンス処理関連の機能が含まれています。シーケンスを使用することで、遅延評価を活用して効率的なデータ処理が可能です。
- kotlin.text.*
- テキスト処理関連の機能が含まれています。文字列の操作やフォーマットなどが簡単に行えます。
また、ターゲットプラットフォームに応じて、追加のデフォルトインポートが行われます。
- JVM
-
- java.lang.*
- Javaの基本的なクラスや機能が含まれています。これにより、Javaとの互換性が確保されます。
- kotlin.jvm.*
- JVM関連の機能が含まれています。
- JS
-
- kotlin.js.*
- JavaScript関連の機能が含まれています。
上記のパッケージは、明示的にインポートすることなく既にインポートされています[2]。
このようなデフォルトのインポートにより、開発者は冗長なコードを書かずに済み、効率的にKotlinの機能を利用することができます。
以下は、デフォルトでインポートされる主要なパッケージの概要を表形式でまとめたものです。
デフォルトでインポートされる主要なパッケージ パッケージ 内容の概要 kotlin.* Kotlinの基本的な機能や標準ライブラリ kotlin.annotation.* アノテーション関連の機能 kotlin.collections.* コレクション関連のクラスや関数 kotlin.comparisons.* 比較関連の機能 kotlin.io.* 入出力関連の機能 kotlin.ranges.* 範囲関連の機能 kotlin.sequences.* シーケンス処理関連の機能 kotlin.text.* テキスト処理関連の機能
また、ターゲットプラットフォームによっては、追加のデフォルトインポートが行われます。
ターゲットプラットフォーム別の追加のデフォルトインポート ターゲットプラットフォーム 追加されるパッケージの概要 JVM java.lang.*: Javaの基本的なクラスや機能 kotlin.jvm.*: JVM関連の機能 JS kotlin.js.*: JavaScript関連の機能
これらのデフォルトインポートは、通常のKotlinプロジェクトで基本的な操作や機能を利用するための便利な手段となります。
インポート
[編集]デフォルトインポートとは別に、各ファイルは独自の import
ディレクティブを含むことができます。
単一の名前でインポートすることができます。
import org.example.Message // Message は無条件にアクセスできます。
または、パッケージ、クラス、オブジェクトなどのスコープのすべてのアクセス可能なコンテンツをインポートすることができます。
import org.example.* // 'org.example' に含まれるすべてのコンテンツにアクセスできるようになります。
名前の衝突がある場合、as キーワードを使用して、衝突するエンティティの名前をローカルに変更することで、曖昧さをなくすことができます。
import org.example.Message // Message にアクセスできるようになります。 import org.test.Message as testMessage // testMessage は 'org.test.Message' を表しています。
import キーワードは、クラスのインポートに限定されません。他の宣言をインポートするためにも使用することができます。
- トップレベルの関数およびプロパティ
- オブジェクト宣言の中で宣言された関数やプロパティ
- enum 定数
トップレベル宣言の可視性
[編集]トップレベル宣言が private とマークされている場合、その宣言が行われたファイルに対してプライベートとなります。
トップレベルオブジェクト
[編集]いかなる関数スコープにも属さないオブジェクトのことを、トップレベルオブジェクト( top level object )と言います[3]。
- トップレベルオブジェクトの前方参照は許される
val a = 10 fun main() { println("a =$a, b = $b") } val b = 32
- 実行結果
a = 10, b = 32
- この例では変数 b の参照が前方参照になっていますが、b はトップレベルオブジェクトなので参照解決されます。
コメント
[編集]Kotlinには、行末で終わるコメントと、複数行に渡ることと、入れ子にすることができるコメントがあります。
- comments.kt
// Kotlinには、行末で終わるコメントと fun main() { /* * 複数行に渡ることと * /* * 入れ子にすることができる * コメントがあります */ */ println("Hello, World!") }
/* ... */
タイプのコメントは、入れ子にできるのが多くのプログラミング言語と異なります。
基本型
[編集]Kotlinでは、任意のインスタンスに対してプロパティーを参照したりメンバー関数を呼出すことができるという意味で、すべてのインスタンスがクラスに属しています。 いくつかの型は特別な内部表現を持つことができます。例えば、数字、文字、ブール値は実行時にプリミティブ値として表現できますが、ユーザーからは参照されるときに自動的にボックス化されるので普通のクラスのインスタンスのように見えます。 これらの型は基本的にJavaのプリミティブに一対一に対応しますが、Stringだけはjava.lang.Stringクラスに対応しています。 基本型は Package kotlin で定義されています。
- Kotlinの基本型
- primitive.kt
fun main() { println("値 : simpleName") println("----------------") arrayOf(true, 2.toByte(), 3.toUByte(), 5.toShort(), 7.toUShort(), 11, 13U, 17L, 19UL, 1.23, 3.14F, 'C', "abc").forEach { println("$it : ${it::class.simpleName}") } }
- 実行結果
値 : simpleName ---------------- true : Boolean 2 : Byte 3 : UByte 5 : Short 7 : UShort 11 : Int 13 : UInt 17 : Long 19 : ULong 1.23 : Double 3.14 : Float C : Char abc : String
基本型[4] 型 Javaの型 リテラル表現 Boolean
boolean
false, true
Byte
byte
Short
short
Int
int
123, 0x17, 0b10110110
Long
long
123L, 0x17L, 0b10110110L
Double
double
1.73205080757,6.62607015e-34
Float
float
1.73205080757f
String
java.lang.String
"Simple sample text."
Char
char
'Q'
論理型
[編集]Kotlinの論理型は Boolean
で
- 偽
false
- 真
true
の2つの値以外は取りえません。
JVMでは、この型の非Nullable値は、プリミティブ型のbooleanの値として表現されます。 論理型と数値型は可換ではないので、制御構造の条件式などでもゼロとの比較を行う必要があります。
数値型
[編集]Kotlinの数値型は、整数型と浮動小数点数型に分かれています。
これらの型は、抽象クラスである Number
クラスから派生しており、具象クラスとして利用されます。
なお、文字型は数値型と可換ではありません。
整数型
[編集]Kotlinの整数型には、符号付き整数型と符号なし整数型があります。
符号なし整数型の名前は、対応する符号付き整数型の名前の先頭に U
を補ったものになります。以下が主な整数型です:
整数型 Byte
符号付き1バイト整数 Short
符号付き2バイト整数 Int
符号付き4バイト整数 Long
符号付き8バイト整数 UByte
符号なし1バイト整数 UShort
符号なし2バイト整数 UInt
符号なし4バイト整数 ULong
符号なし8バイト整数
浮動小数点数型
[編集]Kotlinの浮動小数点数型には、以下の2つがあります:
浮動小数点数型 Float
単精度浮動小数点数;ISO/IEC/IEEE 60559:2011-06 のbinary32 Double
倍精度浮動小数点数;ISO/IEC/IEEE 60559:2011-06 のbinary64
これらの数値型は、数学的な演算や精密な計算に利用され、Kotlin言語において数値処理をサポートする基本的な要素となっています。
文字型
[編集]Charクラスは文字を表すクラスです。'a' のようなKotlinプログラム内の文字リテラルはすべてこのクラスのインスタンスとして実装されています[5][6]。
Char
は、16ビットのUnicode文字を表します。これは、Charがすべての文字をユニコード文字を表すことが出来ない事をしめしています。
- Charで表現できない文字
fun main() { val ascii = 'A' val kanji = '漢' val emoji = '🏝' }
- コンパイル結果
Too many characters in a character literal ''🏝''
- 🏝のUnicodeはU+1F3DDと16ビットを超えているので Char には収容できません。
- 絵文字以外にもサロゲートペアはあり、サロゲートペア以外にも合成文字も16ビットを超えるものがあります。
Kotlinは、Javaの文字エンコーディングシステムを引継いだので、Charの収まらない文字の問題に限らずUnicodeを内部エンコーディングに使っていることに起因する厄介ごとと付き合い続ければなりません。
演算子
[編集]Charでは、いくつかの演算子が定義されています[6]。
- Charクラスの演算子
fun main() { var a = 'K' println("var a = 'K'") a-- println("a-- ⇒ $a") a++ println("a++ ⇒ $a") println("'C' - 'A' ⇒ ${'C' - 'A'}") println("'C' - 2 ⇒ ${'C' - 2}") println("'A' + 2 ⇒ ${'A' + 2}") println("'A'..'C' ⇒ ${'A' .. 'C'}") println("'A'..<'C' ⇒ ${'A' ..< 'C'}") println("'A' + \"BCD\" ⇒ ${'A' + "BCD"}") }
- 実行結果
var a = 'K' a-- ⇒ J a++ ⇒ K 'C' - 'A' ⇒ 2 'C' - 2 ⇒ A 'A' + 2 ⇒ C 'A'..'C' ⇒ A..C 'A'..<'C' ⇒ A..B 'A' + "BCD" ⇒ ABCD
エスケープシーケンス
[編集]特殊文字は、エスケープする \(バックスラッシュ)から始まります。以下のエスケープシーケンスに対応しています。
エスケープシーケンス 表現 意味 \t 水平tab \b バックスペース \n 改行(LF) \r キャリッジリターン(CR) \' シングルクォーテーション \" ダブルクオーテーションマーク \\ バックスラッシュ \$ ドル記号
その他の文字をエンコードする場合は、Unicodeエスケープシーケンス構文を使用します。'\uFF00' を使用します。
文字列型
[編集]String
クラスは文字列を表すクラスです。"abc" のようなKotlinプログラム内の文字列リテラルはすべてこのクラスのインスタンスとして実装されています[7]。
Kotlinでは、文字列( String
)と文字( Char
)とは直接の関係はありません(StringはCharの配列ではありません)。
演算子
[編集]Stringでは、加算演算子を連結として定義されています[8]。
public operator fun plus(other: Any?): String
加算演算子( +
)は、文字列( this )と与えられた他のオブジェクトの文字列表現を連結して得られる文字列を返します[9]。
- 文字列の+演算子
fun main() { val str = "ABC" + 12 println(str + true + listOf(1,2,3)) }
- 実行結果
ABC12true[1, 2, 3]
テンプレートリテラル
[編集]既に紹介したように、Stringクラスのリテラルは "(ダブルクオーテーション)で括った文字列です。
Stringリテラルには変数や式を埋込むことが出来ます。 このように、変数や式が埋込まれたStringリテラルのことをテンプレートリテラルといいます。
- テンプレートリテラル
fun main() { var n = 10 println("変数 n の値は $n です。") println("式 n + 13 の値は ${n + 13} です。") println("${'$'} 自体を書きたいときは、 \\\$ と \$ の直前に \\ を置きエスケープします(\$\$ではありません)。") }
- 実行結果
変数 n の値は 10 です。 式 n + 13 の値は 23 です。 $ 自体を書きたいときは、 \$ と $ の直前に \ を置きエスケープします($$ではありません)。
生文字列
[編集]生文字列( Raw strings )は、改行や任意のテキストを含むことができます。トリプルクォート(”””; triple quote )で区切られ、エスケープを含まず、改行や他の任意の文字を含むことができます[10]。
- 生文字列
fun main() { var n = 10 print( """ 変数 n の値は $n です。 式 n + 13 の値は ${n + 13} です。 ${'$'} 自体を書きたいときは、 \$ と $ の直前に \ を置きエスケープします($$ ではありません)。 """ ) }
- 実行結果
変数 n の値は 10 です。 式 n + 13 の値は 23 です。 $ 自体を書きたいときは、 \$ と $ の直前に \ を置きエスケープします($$ ではありません)。
エスケープシーケンス
[編集]コードポイント
[編集]- 文字列のn番目のコードポイント
fun main() { val str = "ABC漢字🏝𠮷" var i = 0 while (i < str.length) { println(Integer.toHexString(str.codePointAt(i))) i++ } }
- 実行結果
41 42 43 6f22 5b57 1f3dd dfdd 20bb7 dfb7
- codePointAt()でサロゲートペアの2ワード目を読むと…
Array
[編集]Kotlinで配列型は Array で、main() の引数でも使われています[11][12]。
Array(), arrayOf(), arrayOfNulls() や emptyArray() で生成します。
Array()
[編集]- Array()を使ったArrayの生成
fun main() { val ary = Array(5){it} println("ary::class.simpleName ⇒ ${ary::class.simpleName}") println("ary[0]::class.simpleName ⇒ ${ary[0]::class.simpleName}") ary.forEach{print("$it ")} println("") val ary2 = Array(5){(it*it).toString()} println("ary2[0]::class.simpleName ⇒ ${ary2[0]::class.simpleName}") ary2.forEach{print("$it ")} println("") }
- 実行結果
ary::class.simpleName ⇒ Array ary[0]::class.simpleName ⇒ Int 0 1 2 3 4 ary2[0]::class.simpleName ⇒ String 0 1 4 9 16
- Array()はArrayのコンストラクターで、引数として要素数をとり、ブロックが初期化式になります。
プリミティブ型配列
[編集]Kotlinには、IntArray、DoubleArray、BooleanArray、CharArrayなどのプリミティブ型を要素とする配列のクラスが用意されています。 これらを総称してプリミティブ型配列( Primitive type arrays )と呼びます[13]。 プリミティブ型配列は、機能的にはArray<T>のTにプリミティブ型を与えたものと変わりありませんが、ボックス化されないので性能向上とフットプリントの削減が期待できます。 このため、プリミティブ型配列はArrayを継承していません。
StringArray はありません。
- IntArray()を使ったIntArrayの生成
fun main() { val ary = IntArray(5){it} println( """ ary::class.simpleName ⇒ ${ary::class.simpleName} ary[0]::class.simpleName ⇒ ${ary[0]::class.simpleName} ary => $ary ary.joinToString() ⇒ ${ary.joinToString()} ary.contentToString() => ${ary.contentToString()} """ ) }
- 実行結果
ary::class.simpleName ⇒ IntArray ary[0]::class.simpleName ⇒ Int ary => [I@1c6b6478 ary.joinToString() ⇒ 0, 1, 2, 3, 4 ary.contentToString() => [0, 1, 2, 3, 4]
arrayOf()
[編集]- arrayOf()を使ったArrayの生成
fun main() { val ary = arrayOf(1, 9, 3, 5, 23, 1) println("${ary::class.simpleName}") println("${ary[0]::class.simpleName}") for (s in ary) if (s > 10) break else print("$s ") println("") run { ary.forEach{ if (it > 10) return@run else print("$it ") } } println("") var i = 0 while (i < ary.size) ary[i] = i++ ary.forEach{print("$it ")} }
- 実行結果
Array Int 1 9 3 5 1 9 3 5
- arrayOf()は可変長引数の関数で、引数が生成されるArrayの要素になります。
- Arrayの要素の型は、型強制できる最小公倍数的な方になります(例えば Int と Long が混在していたら Long)。
特別な型
[編集]基本型の他にも幾つかの特別な型があります。 これらは、基本型同様 Package kotlin で定義されています。
Any
[編集]AnyはKotlinのクラス階層のルートです。すべてのKotlinクラスはAnyをスーパークラスとして持っています。 クラス定義で継承元を指定しないと Any が暗黙の継承元になります。 また、Anyクラスのオブジェクトは、あらゆるオブジェクトを代入できます。
- AnyのArray
fun main() { arrayOf(4, "abc", 'a', listOf(2,5,6)).forEach{ println("$it(${it::class.simpleName})") } }
- 実行結果
4(Int) abc(String) a(Char) [2, 5, 6](ArrayList)
Unit
[編集]Unitは、何も返さない関数の戻値の型です。JVMでは、Javaのvoid型に相当します。
pub main() : Unit {}
は
pub main() {}
と等価です。
Nothing
[編集]Nothingはインスタンスを持ちません。例えば、ある関数の戻り値がNothingであれば、それは決して戻らない(常に例外を投げる)ことを意味します[14]。
Null安全
[編集]KotlinのNULL安全性(Null safety)とNullableに関して、以下のポイントが重要です。
- Null安全性: Kotlinの目標の一つは、null参照によるエラー(The Billion Dollar Mistakeとも呼ばれる)の危険性を排除することです。Javaを含む多くのプログラミング言語で見られる最も一般的な落とし穴の一つは、null参照のメンバーにアクセスすると、null参照例外が発生することです(JavaではNullPointerException、NPEと呼ばれます)。
- Nullable型と非Nullable型: Kotlinの型システムでは、nullを保持できる参照(Nullable型)と、保持できない参照(非Nullable型)とを区別します。たとえば、String型の通常の変数はnullを保持できませんが、NullableなString型はnullを保持できます。
- Nullable型の宣言: nullを許容する型を宣言するには、型名の後ろに「?」を付けます。例えば、String型のNullableな変数を宣言する場合は、
String?
とします。 - Nullable型の安全な操作: Nullableな変数やプロパティに安全にアクセスする方法として、以下のような手法があります。
?.
演算子: セーフコール演算子は、nullチェックを行い、nullでない場合にのみメンバーにアクセスします。?:
演算子: Elvis演算子は、nullでない場合には左側の値を、nullの場合には右側の値を返します。!!
演算子: null許容型の値を非Nullable型として扱い、nullの場合にはNullPointerExceptionをスローします。
- Null安全性の確認: null安全性の確認は、条件式で明示的に行うことができます。
if (variable != null)
のような形で、nullチェックを行い、それに応じた処理を行います。 - Safe casts: 安全なキャスト演算子
as?
を使用すると、通常のキャストでClassCastExceptionが発生する可能性がある場合でも、nullを返すことができます。
KotlinのNull安全性とNullable型は、プログラミングにおける安全性と信頼性を向上させるための重要な機能です。これらの機能を適切に理解し、利用することで、Nullによるエラーを最小限に抑えることができます。
Javaを含む多くのプログラミング言語における最も一般的な落とし穴の1つは、Null参照のメンバーにアクセスするとNull参照例外が発生することです。Javaでは、これはNullPointerException、略してNPEと呼ばれるものに相当します[15]。
KotlinでNPEが発生する原因として考えられるのは、以下の通りです。
- NullPointerException()を明示的に呼び出した場合。
- 後述する !! 演算子の使用。
- 初期化に関するデータの不整合(以下のような場合)。
- コンストラクターで使用可能な未初期化の this がどこかで渡され使用されている (「リーキング this」)。
- スーパークラスのコンストラクターが、派生クラスの実装で未初期化の状態を使用しているオープンメンバーを呼出す場合。
- Java との相互運用。
- プラットフォーム型のNull参照のメンバにアクセスしようとする。
- Java との相互運用に使用される汎用型の Nullability の問題。例えば、ある Java コードが Kotlin の MutableList<String> に null を追加し、それを操作するために MutableList<String?> が必要になることがあります。
- その他、外部のJavaコードによって引き起こされる問題。
Nullable
[編集]Kotlinの型システムでは、nullを保持できる参照(Null可能参照; nullable references )とそうでない参照(非Null参照; non-null references )を区別しています。例えば、String型の通常の変数はnullを保持できません。
var a: String = "abc" // 通常の初期化ではデフォルトで非nullを意味します // a = null // コンパイルエラー!
nullを許容するには、String?
と書いて変数をnull可能な文字列として宣言します。
var b: String? = "abc" // nullに設定可能 b = null // OK prinlnt(b)
さて、aのメンバー関数を呼び出したり、プロパティーにアクセスしたりしても、NPEが発生しないことが保証されています。
val l = a.length
しかし、bのメンバー関数を呼び出したり、プロパティーにアクセスしたりすると、それは安全ではなく、コンパイラはエラーを報告します。
val l = b.length
それでもそのプロパティにアクセスする必要がありますよね?そのためには、いくつかの方法があります。
条件におけるnullのチェック
[編集]まず、bがnullかどうかを明示的にチェックし、2つの選択肢を別々に処理することができます。
val l = if (b != null) b.length else -1
コンパイラーは実行したチェックの情報を記録し、ifの内部でlengthの呼出しを可能にする。より複雑な条件もサポートされています。
val b: String? = "Kotlin" if (b != null && b.length > 0) { print("文字列の長さは ${b.length}") } else { print("空文字列") }
註:b が immutable な場合 (つまり、チェックから使用までの間に変更されないローカル変数か、 バッキングフィールドを持つオーバーライド不可のメンバー変数) にのみ有効です。
!!演算子
[編集]Not-Null断定演算子(!!)で任意の値をnullでない型に変換し、値がnullの場合は例外をスローします。b!!
と書くと、bの非null値(例えばこの例ではString)を返し、bがnullの場合はNPEを投げます。
val l = b!!.length
このように、NPEを発生させたい場合は、明示的に要求する必要があり、突然発生することはありません。
?.演算子
[編集]fun main() { var str: String? = "Hello" // ?を使ってnullを許容する str = null // 問題なくnullを代入できる val length = str?.length // 安全呼び出し演算子でnullチェックしつつプロパティにアクセス println("Length: $length") }
?.
演算子は、null安全性を保証しつつ、str
変数がnullでない場合に length
プロパティにアクセスします。str
がnullの場合、length
には null
が代入されます。
このように、?.
演算子はnull値を安全に処理するために使用されます。例えば、Javaの場合、nullチェックを行わないとNullPointerExceptionが発生しますが、Kotlinでは ?.
演算子を使用することで、nullに対する操作を安全に行うことができます。
識別子
[編集]変数の名前のような名前を識別子( identifiers )と呼びます[16]。 変数ほかに、関数、クラス、クラスのメンバー、クラスのメンバー関数、enum、ラベルなどの名前も識別子です。
- 同じ名前空間の中では識別子は重複できません。
- 識別子に使える文字は、英数字・_(アンダーバー)・Unicode文字です。
- 識別子の最初に数字を使うことはできません。
- 識別子の大文字小文字は区別されます。
- キーワードの使用には制限があります。
- キーワードや空白を含む文字列など上のルールに従わない文字列は、`(バッククオーテーション)で囲むと識別子として使うことができます。
変数
[編集]Kotlinでは、変数は使う前にかならず宣言する必要があります。
val と var
[編集]val
[編集]- 変数を使った単純なコード
fun main() { val hello = "Hello, World!" println(hello) }
- 実行結果
Hello, World!
- Hello worldの例と結果は同じですが、変数helloを導入しています。
- 変数 hello の宣言
val hello = "Hello, World!"
- イミュータブル変数の宣言は、この様に:
val 識別子 = 初期化式
- の形式をとります。
- 変数 hello の値の参照
println(hello)
- の様に識別子をそのまま書くことで、値(この場合は "Hello, World!")を参照できます。
- キーワード
val
を使って宣言された変数はイミュータブル( Immutable )です。- イミュータブルというのは、一度値が決めたら変更できないという意味です。
- イミュータブルな変数を「定数」ということがありますが、リテラルのことを定数ということもあるので、ここでは「イミュータブルな変数」と呼びます。
var
[編集]- ミュータブルな変数を使ったコード
fun main() { var hello = "Hello, World!" println(hello) hello = "Hello, Kotlin!" println(hello) }
- 実行結果
Hello, World! Hello, Kotlin!
- 変数 hello の宣言
var hello = "Hello, World!"
- ミュータブルな変数の宣言は、この様に:
var 識別子 = 初期化式
- の形式をとります。
- キーワード
var
を使って宣言された変数はイミュータブル( Immutable )です。- ミュータブルというのは、変数の値を何度でも変更できるという意味です。
- 変数 hello に新しい値を代入
hello = "Hello, Kotlin!"
- Kotlinでは
=
が代入演算子です。- 代入の前の hello の値は
"Hello, World!"
でしたが、代入の後は"Hello, Kotlin!"
になります。
- 代入の前の hello の値は
型推論
[編集]いままでの例で既に型推論( type inference ) は使われています。 変数を宣言するときに、特に型を明示しませんでしたが、初期化式の型から変数の型を特定していたのです。
型アノテーション
[編集]初期化式は省略可能です。 その場合は変数の型がわからないので型アノテーション( type annotation )が必要になります。
- 型アノテーションを伴った変数宣言
fun main() { var hello : String hello = "Hello, World!" println(hello) hello = "Hello, Kotlin!" println(hello) }
- 実行結果
Hello, World! Hello, Kotlin!
- 型アノテーションを伴った変数 hello の宣言
var hello : String
: String
が型アノテーションで
- 型アノテーションを伴った変数の宣言は、この様に:
var 識別子 : 型
- の形式をとります。
- 型アノテーションをイミュータブルな変数の宣言でも行えますが、事実上初期化式が必須なのでドキュメント性を高める以外の意味は希薄です。
シャドーイング
[編集]シャドーイング( Shadowing )とは、スコープ内の2つの宣言が同じ名前になり、より内側の識別子が外側の識別子を隠すことです。
- コード例
fun main() { var i = 10 for (i in 1..3) println("for内: i = $i") println("for外: i = $i") }
- コンパイラーの警告
Main.kt:4:10: warning: name shadowed: i for (i in 1..3) ^
- 実行結果
for内: i = 1 for内: i = 2 for内: i = 3 for外: i = 10
- ループ変数 i と、2行目で宣言された変数 i の名前が衝突しいています。
- この様に名前が衝突した場合、スコープの内側のオブジェクトが参照されます。
- コンパイラーはシャドーイングを発見すると
warning: name shadowed: 識別子
と(エラーでなく)警告します。
多くのシャドーイングは無害ですが…
- ありがちな間違え
fun main() { for (i in 1..3) for (i in 1..4) println("(i, i) = ($i, $i)") }
- コンパイラーのエラー出力
Main.kt:3:14: warning: name shadowed: i for (i in 1..4) ^
- 修正例
fun main() { for (i in 1..3) for (j in 1..4) println("(i, j) = ($i, $j)") }
- 行列式を扱っていると、よくやらかします。
分解宣言
[編集]オブジェクトを複数の変数に分解して初期化する宣言する方法があり、分解宣言( Destructuring declarations )と呼ばれます[17]。
- 分解宣言の例
fun main() { val (a, b) = Pair(3, 4) val (c, d) = Pair("abc", 3.14) val (e, f) = 'C' to null val (g, h, i) = Triple(1,2,3) val (j, k, l, m) = List(4){it*2} print( """ a = $a, b = $b c = $c, d = $d e = $e, f = $f g = $g, h = $h, i = $i j = $j, k = $k, l = $l, m = $m """ ) }
- 実行結果
a = 3, b = 4 c = abc, d = 3.14 e = C, f = null g = 1, h = 2, i = 3 j = 0, k = 2, l = 4, m = 6
- to は infix 宣言された関数です。
演算子
[編集]演算子の優先順位
[編集]演算子の優先順位[18] 優先順位 種類 記号 高い 後置 ++
,--
,.
,?.
,?
前置 -
,+
,++
,--
,!
, label型 :
,as
,as?
乗除算 *
,/
,%
加減算 +
,-
範囲 ..
,..<
中置関数(Infix function) simpleIdentifier エルビス ?:
Named checks in
,!in
,is
,!is
比較 <
,>
,<=
,>=
一致不一致 ==
,!=
,===
,!==
Conjunction &&
Disjunction ||
スプレッド演算子 *
低い 代入演算 =
,+=
,-=
,*=
,/=
,%=
演算子オーバーロード
[編集]Kotlinでは、演算子はメソッド形式の別名を持ちます。
例えば、a + b
は a.plus(b)
とも書けます。
この plus メンバー関数を再定義すると、演算子オーバーロードができます[19]。
- コード例
fun main() { class Point(val x : Int = 0, val y : Int = 0) { override fun toString() ="Point(x=$x, y=$y)" operator fun plus (other: Point) = Point(x + other.x, y + other.y) operator fun minus(other: Point) = Point(x - other.x, y - other.y) operator fun unaryMinus() = Point(-x, -y) override fun equals(other: Any?) = when { this === other -> true other !is Point -> false x != other.x -> false else -> y == other.y } } val p = Point(15, 25) val q = Point(20, 30) print( """ p => $p p.x => ${p.x}, p.y => ${p.y} q => $q p.plus(q) => ${p.plus(q)} p + q => ${p + q} 12 + 5 => ${12 + 5} 12.plus(5) => ${12.plus(5)} ---- p - q => ${p - q} -p => ${-p} p == q => ${p == q} p != q => ${p != q} p == Point(15,25) => ${p == Point(15,25)} p != Point(15,25) => ${p != Point(15,25)} """ ) }
- 実行結果
p => Point(x=15, y=25) p.x => 15, p.y => 25 q => Point(x=20, y=30) p.plus(q) => Point(x=35, y=55) p + q => Point(x=35, y=55) 12 + 5 => 17 12.plus(5) => 17 ---- p - q => Point(x=-5, y=-5) -p => Point(x=-15, y=-25) p == q => false p != q => true p == Point(15,25) => true p != Point(15,25) => false
演算子は、もちろん加算だけではありません。
単項演算子 式 メソッド形式 +a
a.unaryPlus()
-a
a.unaryMinus()
!a
a.not()
a++
a.inc()
a--
a.dec()
算術演算 式 メソッド形式 a + b
a.plus(b)
a - b
a.minus(b)
a * b
a.times(b)
a / b
a.div(b)
a % b
a.rem(b)
a..b
a.rangeTo(b)
a..<b
a.rangeUntil(b)
包含 式 メソッド形式 a in b
b.contains(a)
a !in b
!b.contains(a)
インデックスによる要素参照 式 メソッド形式 a[i]
a.get(i)
a[i, j]
a.get(i, j)
a[i_1, ..., i_n]
a.get(i_1, ..., i_n)
a[i] = b
a.set(i, b)
a[i, j] = b
a.set(i, j, b)
a[i_1, ..., i_n] = b
a.set(i_1, ..., i_n, b)
関数的な呼出し 式 メソッド形式 a()
a.invoke()
a(i)
a.invoke(i)
a(i, j)
a.invoke(i, j)
a(i_1, ..., i_n)
a.invoke(i_1, ..., i_n)
代入演算 式 メソッド形式 a += b
a.plusAssign(b)
a -= b
a.minusAssign(b)
a *= b
a.timesAssign(b)
a /= b
a.divAssign(b)
a %= b
a.remAssign(b)
一致・不一致 式 メソッド形式 a == b
a?.equals(b) ?: (b === null)
a != b
!(a?.equals(b) ?: (b === null))
比較演算 式 メソッド形式 a > b
a.compareTo(b) > 0
a < b
a.compareTo(b) < 0
a >= b
a.compareTo(b) >= 0
a <= b
a.compareTo(b) <= 0
制御構造
[編集]分岐
[編集]Kotlinは、if と when の2つの分岐構文を持ち、両方とも値を返す式です。
if
[編集]if式は、条件式に基づき分岐し、分岐先の式を評価します。 if式の値は、分岐先の式の値です(C言語系の三項演算子に相当する働きをします)。 if式の値を右辺値化した場合、else節は必須です。
- 構文
if-expr := if '(' 条件式 ')' 式1 [ else 式2 ]
- if式の例
fun main(args: Array<String>) { val i = 0 if (i == 0) println("zero") else println("non zero") println(if (i == 0) "零" else "非零" ) }
- 実行結果
zero 零
条件式の条件
[編集]- 論理型
- Nullableな型
でなければいけません。
- 条件式に整数を使うと
fun main(args: Array<String>) { val i = 0 if (i) println("non zero") }
- コンパイルエラー
Main.kt:4:9: error: type mismatch: inferred type is Int but Boolean was expected if (i) ^
- Kotlinでは、整数は自動的には論理型に変換されないので
if (i != 0) println("non zero")
- とします。
when
[編集]when式を使って条件分岐をすることができます。 when 式は、when に与えられた式(境界値)に最初にマッチするパターンに結びついた値を返すパターンマッチングです。 式が省略されると、パターンの条件が最初に真になるパターンに結びついた値を返します。
- whenの例
fun main() { val ary = arrayOf(1, 'X', 3.14, "abc", arrayOf(1,2,3), true) for (obj in ary) { when (obj) { is Number, is Boolean -> println(obj) is Char -> println("'$obj'") is String -> println("\"$obj\"") is Array<*> -> println(obj.joinToString(prefix="[", postfix="]")) else -> println(obj::class.simpleName) } } }
- 実行結果
1 'X' 3.14 "abc" [1, 2, 3] true
- when式は、いくつかの論理条件に応じて、複数の異なる制御構造体(ケース)のうちの1つを評価することができるという点で、条件式と類似しています[20]。
- 重要な違いは、when式は複数の異なる条件とそれに対応する制御構造体(control structure bodies; CSB)を含むことができることです。
- when式には、境界値付きと境界値なしの2種類の形式があります。
- 境界値(bound value;whenキーワードの後の括弧で囲まれた式)なしのwhen式は、whenエントリからの条件に基づいて異なるCSBのうちの1つを評価します。
- 各 when エントリは、boolean 条件(または特殊な else 条件)とそれに対応する CSB から構成されます。
- when項目は出現順にチェックされ評価され、条件が真と評価された場合、対応するCSBが評価され、when式の値はCSBの値と同じになり、残りのすべての条件と式は評価されません。
- whenパターン式のパターンの後ろに break は不要です(フォールスルーしませんし、することはできません)。
- もし break を書くと、when式の外のループ式からの脱出になります(フォールスルーしてしまう言語には、できなかったこと)。
whenは式なので以下のように書換えることができます。
- when式の例
fun main() { val ary = arrayOf(1, 'X', 3.14, "abc", arrayOf(1,2,3), true) for (obj in ary) { val s = when (obj) { is Number, is Boolean -> obj is Char -> "'$obj'" is String -> "\"$obj\"" is Array<*> -> obj.joinToString(prefix="[", postfix="]") else -> obj::class.simpleName } println(s) } }
区切子 ->
を使った構文の左辺の条件には、以下のようなバリエーションがあります。
- when式の条件の構文
値 値1, 値2... , 値n in 範囲式 !in 範囲式 is 型 !is 型 else
- 値は、定数である必要はなくメソッドでも構いません。
- 式は、単式だけでなくブロック式
{...}
でも構いません。
引数のないwhen
[編集]when
文は、多くの条件分岐をシンプルに表現するための強力な構造です。境界値が省略された場合、true
が境界値とみなされます。
- 引数のないwhenの例
fun main() { val a = 1 val b = 2 when { a == 0 -> println("a == 0") a == 1 && b == 1 -> println("a == 1 && b == 1") a == 1 && b == 2 -> println("a == 1 && b == 2") else -> println("else") } }
- 実行結果
a == 1 && b == 2
- 上記のコードでは、
when
文が引数を取らずに条件分岐を行っています。最初の条件がfalse
で、次の条件がtrue
となり、そのブロックが実行されます。 - ifを使った等価なコード
- 同じ条件分岐を
if
文を使って表現すると以下のようになります。 fun main() { val a = 1 val b = 2 if (a == 0) println("a == 0") else if (a == 1 && b == 1) println("a == 1 && b == 1") else if (a == 1 && b == 2) println("a == 1 && b == 2") else println("else") }
when
文を使うことで、このような条件分岐をより簡潔に表現できます。
境界値を省略した例
[編集]境界値を省略した例として、以下のコードを見てみましょう。
fun main() { val x = 5 val result = when { x > 0 -> "Positive" x < 0 -> "Negative" else -> "Zero" } println(result) }
このコードでは、when
文が x
の値に基づいて文字列を返します。境界値が省略されているため、条件が true
のブロックが実行されます。
when
文でループを脱出する例
[編集]when
文を使用してループから脱出する例もあります。以下は、when
文を使って特定の条件でループを脱出する例です。
fun main() { val numbers = listOf(1, 2, 3, 4, 5) for (number in numbers) { when (number) { 3 -> { println("Found 3, breaking the loop.") break } else -> println("Processing $number") } } }
このコードでは、when
文を使用して number
が3の場合にループから脱出します。
enum
な式が境界値に与えられた例
[編集]enum
クラスを使った when
文の例も挙げてみましょう。
enum class Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } fun main() { val today = Day.WEDNESDAY val activity = when (today) { Day.MONDAY, Day.TUESDAY -> "Working" Day.WEDNESDAY -> "Meeting" Day.THURSDAY, Day.FRIDAY -> "Coding" Day.SATURDAY, Day.SUNDAY -> "Relaxing" } println("Today's activity: $activity") }
このコードでは、enum
クラス Day
を使用して、曜日に応じた活動を選択しています。when
文が enum
の各ケースを処理し、対応する活動が選択されます。
繰返し処理
[編集]Kotlinには、while・do-while と for の3つの繰返し構文があります[21]。これらは文で、値を返すことはできません。
while
[編集]whileは、条件式が true の間、式を評価しつづけます[22]。
- 構文
while-stmt := while '(' 条件式 ')' 式
- 条件式は、Boolean 型でなければいけません。
- whileの例
fun main(args: Array<String>) { var i = 0 while (i < 5) { println(i) i += 1 } println("last = $i") }
- 実行結果
0 1 2 3 4 last = 5
do-while
[編集]do-whileは、条件式が true の間、式を評価しつづけるという意味では、whileと同じですが、条件式がループの最後にある、つまり条件が成立しなくても1周はループが回ることが異なります[23]。
- 構文
while-stmt := do 式 while '(' 条件式 ')'
- 条件式は、Boolean 型でなければいけません。
- do-whileの例
fun main() { var i = 0 do { i++ println("i = $i") } while (i < 0) }
- 実行結果
i = 1
for
[編集]Kotlinのforはw:foreach文タイプのループ構文で、C言語の for(;;) とは異なる構文です[24]。
- 構文
for (ループ変数 in コレクション) 式
- 範囲コレクションとforを組合せた例
fun main(args: Array<String>) { for (i in 1..5) println(i) println((1..5).javaClass.kotlin) }
- 実行結果
1 2 3 4 5 class kotlin.ranges.IntRange
- iの様なループ変数は、forがスコープになります。
- ここでは、型指定を省略しているので、型推論されコレクションの要素型になります。省略せず、
for (i : Int in 1..5) println(i)
- とすることもできます(ただし、異種コレクションだと要素型はUnion型になり宣言が複雑になるので、コードレビューの時に意図を明確にするなどの想起がない限り、型推論に任せるのが常です)。
- ループ変数は、varやvalを前置することができません。
- ループ変数には、代入できません。
for と等価な while
[編集]- for と等価な while
for (ループ変数 in コレクション) { println(ループ変数) } // は、以下と等価 val イテレーター = コレクション.iterator() while (イテレーター.hasNext()) { val ループ変数 = イテレーター.next() println(ループ変数) }
関数
[編集]関数は、キーワード fun
を使って定義します[25]。
関数定義
[編集]- 関数定義と呼出しの例
fun ipow(base: Int, times: UInt) : Int { var result = 1 var i = 0U while (i < times) { result *= base i++ } return result } fun main() { println("ipow(2, 3) = ${ipow(2, 3U)}") println("ipow(10, 4) = ${ipow(10, 4U)}") }
- 実行結果
ipow(2, 3) = 8 ipow(10, 4) = 10000
- 関数 ipow 定義の冒頭
fun ipow(base: Int, times: UInt) : Int {
- Intの仮引数 base と、UIntの仮引数 times を受取り Int の戻値を返すと読めます。
- UIntに下のは、マイナスの指数を整数の累乗で扱いたくなかったため、3U や 4U のような符号なし整数リテラルの例にもなってます。
- main() の中で
ipow(2, 3U)
やipow(10, 4U)
の様に呼出しています。- 仮引数と実引数の型の一致は、符号まで求められます。
- 関数定義の構文(1)
fun 関数名(仮引数リスト) : 戻値型 { // 文 … return 戻値式 }
- さて、main関数はこの構文から逸脱しています。
: 戻値型
がありませんし、return 戻値式
も見当たりません。 return 戻値式
を省略したときの関数の戻値型はUnit
になります。- また、
: Unit
は省略可能です。ということで main の定義はさっぱりしたものになります。
ボディが単一の式からなる関数定義
[編集]関数のボディが単一の式からなる場合、{ return 式 }
を、= 式
と書くことができます[26]。
- 関数定義の構文(2)
fun 関数名(仮引数リスト) : 戻値型 = 式
- ボディが単一の式からなる関数定義の例
fun add2(n: Int) : Int = 2 + n fun main() { println("add2(3) = ${add2(3)}") }
- 実行結果
add2(3) = 5
初見だと驚きますが、関数型プログラミング風の書き方が簡素にできます。 特に、ifやwhenが値を返すことができる式であることが効いてきます。
戻値型の省略
[編集]ボディが単一の式からなる関数定義では、戻値式の型が推論できる場合が多いので、戻値型を省略できる場合があります。
- 戻値型の省略
fun add2(n: Int) = 2 + n fun main() { println("add2(3) = ${add2(3)}") }
再帰関数は戻値型を省略できない
[編集]再帰関数の戻値型を省略しようとすると、自分自身が型不明な項になりコンパイルできません。
- 戻値型の省略
fun power(n: Int, i: Int) = when { i < 0 -> throw Exception("Negative powers of integers cannot be obtained.") i == 0 -> 1 i == 1 -> n else -> n * power(n, i - 1) } fun main() { (0 .. 7).forEach{ println("power(2, $it) => ${power(2, it)}") } }
- コンパイル結果
Main.kt:5:17: error: type checking has run into a recursive problem. Easiest workaround: specify types of your declarations explicitly else -> n * power(n, i - 1) ^
- 意訳
- 【エラー】型チェックで再帰問題が発生しました。最も簡単な回避策は、宣言の型を明示的に指定することです。
- 戻値型を明示
fun power(n: Int, i: Int) : Int = when { i < 0 -> throw Exception("Negative powers of integers cannot be obtained.") i == 0 -> 1 i == 1 -> n else -> n * power(n, i - 1) } fun main() { (0 .. 7).forEach{ println("power(2, $it) => ${power(2, it)}") } }
- 実行結果
power(2, 0) => 1 power(2, 1) => 2 power(2, 2) => 4 power(2, 3) => 8 power(2, 4) => 16 power(2, 5) => 32 power(2, 6) => 64 power(2, 7) => 128
引数はイミュータブル
[編集]関数の引数はイミュータブルです。 これは Zig も同じで、新興言語は不用意な書換えによる古参言語で度々アクシデントのもととなった引数の破壊を永久になくしたいようです。
引数のディフォルト値
[編集]関数には、引数にディフォルト値を設定できます。これにより、呼び出し側は引数を指定しなくても関数を呼び出すことができます。
たとえば、次の関数は、名前と年齢の2つの引数を取ります。名前には「John Doe」というデフォルト値が設定されています。
fun sayHello(name: String = "John Doe", age: Int) { println("Hello, $name! You are $age years old.") }
この関数を呼び出すには、名前を指定するか、デフォルト値を使用できます。
sayHello() // Hello, John Doe! You are 0 years old. sayHello("Jane Doe") // Hello, Jane Doe! You are 0 years old.
関数の引数にデフォルト値を設定すると、呼び出し側がすべての引数を指定する手間を省くことができます。また、関数の使い方を覚えやすくすることもできます。
関数呼出し
[編集]関数指向の構文
[編集]関数名(実引数リスト)
次のコードは、関数指向の構文を使用して関数を呼び出す方法を示しています。
fun main(args: Array<String>) { val sum = add(1, 2) println(sum) // 3 } fun add(x: Int, y: Int): Int { return x + y }
このコードは、add
関数を呼び出して、2つの引数 1
と 2
を渡します。add
関数は、これらの引数を受け取った後、それらを加算して結果を返します。関数 main
は、結果をコンソールに出力します。
メソッド指向の構文
[編集]インスタンス.関数名(実引数リスト)
- メソッドあるいは拡張関数の呼出しはドット記法になります。
次のコードは、メソッド指向の構文を使用して関数を呼び出す方法を示しています。
fun main(args: Array<String>) { val person = Person("John Doe", 30) println(person.greet()) // Hello, John Doe! } class Person(val name: String, val age: Int) { fun greet() = "Hello, $name!" }
このコードは、Person
クラスのインスタンスを作成し、name
プロパティに John Doe
という値、age
プロパティに 30 という値を設定します。次に、greet
メソッドを呼び出して、インスタンスの名前を出力します。
関数引数のある構文
[編集]- 関数呼出しの構文(2)
関数名(実引数リスト) 関数型実引数
- 関数呼出しの構文(2’)
インスタンス.関数名(実引数リスト) 関数型実引数
- メソッドあるいは拡張関数の呼出しはドット記法になります。
- 関数にブロック(に擬態したラムダ関数)を渡す
fun Int.times(action: (Int) -> Unit) = (0 ..< this).forEach(action) fun main() { 5.times{ println("Hello -- $it") } }
- 実行結果
Hello -- 0 Hello -- 1 Hello -- 2 Hello -- 3 Hello -- 4
- RubyのInteger#timesをKotlinに移植してみました。
- this の数だけ関数仮引数 action を実行します。
- 呼出は
42.times{ /* Action */ }
の形式になります。
- この機能や infix 関数修飾子・ラムダ関数・拡張関数のおかげで Kotrin は、あたかも「構文を後からプログラマーが拡張することができる言語」のように振舞います。
仮引数の名前を使った実引数の指定
[編集]Kotlin は関数を呼出す時、引数を名前で指定する事ができます。
- キーワード引数
fun main() { val ary = Array(3){it} println(ary) println(ary.toString()) println(ary.joinToString()) println(ary.joinToString("A", "B", "C")) println(ary.joinToString(prefix="🌞", separator="⭐", postfix="🌛")) }
[Ljava.lang.Integer;@5ca881b5 [Ljava.lang.Integer;@5ca881b5 0, 1, 2 B0A1A2C 🌞0⭐1⭐2🌛
- Arrayクラスのインスタンスを
println()
に渡すとワヤクチャな文字列を表示します。 - これは、Any.toString() をオーバーライドした Array.toString() が暗黙に呼出された結果です。
- Array.joinString() を使うと、
0, 1, 2
と表示されます - Array.joinString() は、先頭・区切り・末尾を引数で指定できます。
- …区切り・先頭・末尾の順だったようです。
- このように、引数の順序と意味を正確に覚えておくのは面倒なので、
prefix=
の様に関数定義の仮引数の名前で実引数を指定することができます。
infix
[編集]関数修飾子 infix
を使うと、中置表現の関数呼出しを行うことができるようになります。外観は文法を拡張したかのような印象をうけます。
実引数1 関数名 実引数2
- infix 関数修飾子で修飾された関数は、中置表現での呼出しができます。
- infix な関数の呼出し例
fun main() { val r = 4 downTo 0 println("r => $r") println("r::class.simpleName => ${r::class.simpleName}") r.forEach{ println("Hello -- $it") } val q = 0.downTo(-4) q.forEach{ println("Goodbye -- $it") } }
- 実行結果
r => 4 downTo 0 step 1 r::class.simpleName => IntProgression r => 4 downTo 0 step 1 r::class.simpleName => IntProgression Hello -- 4 Hello -- 3 Hello -- 2 Hello -- 1 Hello -- 0 Goodbye -- 0 Goodbye -- -1 Goodbye -- -2 Goodbye -- -3 Goodbye -- -4
downTo
は、二項演算子に擬態していますがinfix fun Int.downTo(to: Byte): IntProgression
と宣言された拡張関数です[27]。4 downTo 0
は4.downTo(0)
と同じ意味です。- infix 修飾できるのは、メソッドあるいは拡張関数です。
- 一般の関数に infix を適用しようとすると
infix fun mult(n: Int, m: Int) : Int = n * m fun main() { println("12 mult 2 => ${12 mult 2}") }
- コンパイル結果
Main.kt:1:1: error: 'infix' modifier is inapplicable on this function: must be a member or an extension function infix fun mult(n: Int, m: Int) : Int = n * m ^ Main.kt:4:32: error: unresolved reference: mult println("12 mult 2 => ${12 mult 2}") ^
- 意訳
- ’infix' 修飾子はこの関数には適用できません。メンバーか拡張関数ではないからです。
関数スコープ
[編集]関数スコープは、変数や関数が定義され、利用可能な範囲を指します。Kotlinでは、関数スコープ内で定義された変数や関数はそのスコープ内でのみアクセス可能で、外部のスコープからは見えません。
以下に、関数スコープの基本的な特徴と例を示します。
関数スコープの特徴
[編集]- 変数の有効範囲: 関数スコープ内で宣言された変数は、その関数内でのみ有効です。関数外からはアクセスできません。
- 関数の有効範囲: 同様に、関数スコープ内で宣言された関数もそのスコープ内でのみ呼び出すことができます。
- 変数のシャドーイング: 関数スコープ内で同名の変数を再宣言すると、外部の変数は一時的に「シャドーイング」され、関数内の変数が優先されます。
関数スコープの例
[編集]fun main() { // 外部のスコープ val outerVariable = "I am outside!" println(outerVariable) // 外部の変数にアクセス myFunction() // 関数外からは関数スコープ内の変数や関数にアクセスできない // println(innerVariable) // コンパイルエラー // innerFunction() // コンパイルエラー } fun myFunction() { // 関数スコープ val innerVariable = "I am inside!" println(innerVariable) // 関数内から外部の変数にアクセス fun innerFunction() { println("I am an inner function!") } innerFunction() // 関数内から関数を呼び出し }
この例では、main
関数が外部のスコープで、myFunction
が関数スコープ内で宣言されています。main
関数内では outerVariable
にアクセスでき、myFunction
内では innerVariable
と innerFunction
にアクセスできます。ただし、逆は成り立ちません。
関数スコープは、変数や関数の可視性を制御し、プログラムの構造を整理する上で重要な役割を果たします。関数ごとにスコープが分離されるため、変数や関数の名前の衝突を防ぎ、コードの保守性を向上させます。
可変長引数
[編集]可変長引数( Variable-Length Arguments )は、関数が異なる数の引数を受け入れることを可能にする Kotlin の機能です。これは vararg
キーワードとスプレッド演算子( spread operator[28] )を使用して実現されます。
- 可変長引数のコード例
fun main() { myVaPrint("abc", "def", "xyz") } fun myVaPrint(vararg values: String) { for (s in values) println(s) }
- 上記の例では、
myVaPrint
関数が可変長引数values
を受け入れるように定義されています。この関数は、与えられた引数を文字列として順番に出力します。 - 実行結果
abc def xyz
このように、関数を呼び出す際に異なる数の引数を渡すことができます。可変長引数は、同じ型の引数が複数個ある場面で便利です。
スプレッド演算子
[編集]スプレッド演算子( Spread Operator )は、リストや配列などの要素を展開して、可変長引数として渡すための演算子です。
fun main() { val values = arrayOf("abc", "def", "xyz") myVaPrint(*values) }
この例では、arrayOf
で作成した配列の要素をスプレッド演算子 *
を使って myVaPrint
関数に渡しています。これにより、配列の各要素が可変長引数として関数に渡されます。
可変長引数とスプレッド演算子の組み合わせにより、異なる数の引数を柔軟に扱うことができ、関数の再利用性を向上させます。
高階関数
[編集]引数あるいは戻値あるいは両方が関数の関数を高階関数()と呼びます[29]。
- 関数にブロック(に擬態したラムダ関数)を渡す
fun Int.times(action: (Int) -> Unit) = (0 ..< this).forEach(action) fun main() { 5.times{ println("Hello -- $it") } }
- 実行結果
- パラメーター action が関数です。
- 型は
(Int) -> Unit
のように(引数型リスト) -> 戻値型
- 関数 times 本体の、
(0 ..< this).forEach(action)
整数範囲のメソッドforEach
に関数action
を渡しているので、これも高階関数です。- forループで書くと
fun Int.times(action: (Int) -> Unit) { for (0 ..< this) action(it) } :: 長い!
- このコードは関数呼出しからの再録です。
ラムダ
[編集]ラムダ式( lambda expressions )では、波括弧の周囲と、パラメータと本体を分ける矢印の周囲に空白を使用する必要があります。ラムダを1つだけ指定する場合は、可能な限り括弧で囲んでください[30]。
また、ラムダのラベルを指定する場合、ラベルと中括弧の間にスペースを入れてはいけません。
- ラムダ式の例
fun main() { val pow = { x: Int -> x * x }; println("pow(42) => ${pow(42)}"); }
無名関数
[編集]上記のラムダ式構文には、関数の戻値の型を指定する機能がひとつだけ欠けています。ほとんどの場合、戻値の型は自動的に推測されるため、この指定は不要です。しかし、明示的に指定する必要がある場合は、別の構文として無名関数( Anonymous functions )を使用することができます[31]。
- 無名関数の例
fun(a: Int, b: Int): Int = a * b // あるいは fun(a: Int, b: Int): Int { return a * b; }
- JavaScriptの関数リテラルと似ていますが、JSには戻値型はないので動機が違います(JSではラムダがthisを持てないので関数リテラルの出番があります)。
クロージャー
[編集]ラムダ式や無名関数(ローカル関数やオブジェクト式も同様)は、外部スコープで宣言された変数を含むクロージャー( Closures )にアクセスすることができます。クロージャーに取り込まれた変数は、ラムダ式で変更することができます[32]。
- クロージャーの例
fun main() { var sum = 0 IntArray(10){2 * it - 10}.filter{ it > 0 }.forEach { sum += it } print(sum) }
inline
[編集]高階関数を使用すると、ある種の実行時ペナルティーが課せられます。各関数はオブジェクトであり、クロージャーを捕捉します。クロージャー( closure )とは、関数本体でアクセス可能な変数のスコープです。メモリー確保(関数オブジェクトとクラスの両方)と仮想呼出しは、実行時オーバーヘッドを発生させます[33]。
しかし、多くの場合、ラムダ式をインライン化することで、この種のオーバーヘッドをなくすことができます。
- 関数呼出しのコードにinlineを前置
inline fun Int.times(action: (Int) -> Unit) = (0 ..< this).forEach(action)
- この例は、拡張関数のインライン化です。
再帰的呼出し
[編集]Kotlin は、特に修飾辞なしに関数を再帰呼び出しできます。
- フィボナッチ
fun main() { var i = 1 while (i < 30) { println("$i! == ${fib(i)}"); i++ } } fun fib(n: Int) : Int = if (n < 2) n else fib(n - 1) + fib(n - 2)
- 実行結果
1! == 1 2! == 1 3! == 2 4! == 3 5! == 5 6! == 8 7! == 13 8! == 21 9! == 34 10! == 55 11! == 89 12! == 144 13! == 233 14! == 377 15! == 610 16! == 987 17! == 1597 18! == 2584 19! == 4181 20! == 6765 21! == 10946 22! == 17711 23! == 28657 24! == 46368 25! == 75025 26! == 121393 27! == 196418 28! == 317811 29! == 514229
- フィボナッチ数自体が再帰的な式なので、関数定義の式構文が使えました。
tailrec
(末尾再帰最適化)
[編集]tailrec
は、Kotlinのキーワードで、末尾再帰最適化(Tail Recursion Optimization)を実現するために使用されます。末尾再帰最適化は、再帰関数が最後の操作として再帰呼び出しを行う場合に、スタックの消費を減少させる最適化手法です。これにより、スタックオーバーフローを避けることができます。
- 末尾再帰関数の例
fun main() { val result = factorial(5) println("Factorial: $result") } tailrec fun factorial(n: Int, accumulator: Long = 1): Long { return if (n == 0) { accumulator } else { factorial(n - 1, n * accumulator) } }
上記の例では、factorial
関数が末尾再帰関数として宣言されています。再帰呼び出しは末尾で行われており、コンパイラが最適化を行うことが期待されます。
tailrec
の制約
[編集]tailrec
を使用するためにはいくつかの制約があります。
- 関数は再帰呼び出しの最後に自分自身を呼び出さなければなりません。
- 再帰呼び出しの後に他の処理(例: 演算、代入)があってはいけません。
- 制約を満たさない例
// コンパイルエラー: "This call is not allowed here" tailrec fun invalidFactorial(n: Int): Int { return if (n == 0) { 1 } else { n * invalidFactorial(n - 1) // 制約を満たしていない } }
- 制約を満たす例
tailrec fun validFactorial(n: Int, accumulator: Int = 1): Int { return if (n == 0) { accumulator } else { validFactorial(n - 1, n * accumulator) // 制約を満たしている } }
tailrec
を使うと、再帰関数の性能を向上させることができます。ただし、制約を理解し、満たすことが重要です。tailrec
が適用される場合、コンパイラはループに変換し、スタックの使用を最小限に抑えます。
拡張
[編集]Kotlinでは、クラスやインターフェースを継承したり、Decoratorのようなデザインパターンを使わずに、新しい機能を拡張することができます。これは、拡張( extensions )と呼ばれる特別な宣言によって実現されます[34]。
拡張関数
[編集]拡張関数( Extension functions )を宣言するには、その名前の前に拡張される型を示すレシーバー型をつけます[35]。以下は、Array<Int>にrotate関数を追加したものです。
- Array<Int>にrotate()を定義
fun main() { fun Array<Int>.rotate() { val t = this[0] var i = 1 while (i < this.size) { this[i - 1] = this[i] i++ } this[this.size - 1] = t } var ary = arrayOf(2, 3, 5, 7, 11) println("ary = ${ary.map{it.toString()}.joinToString(" ")}") ary.rotate() println("ary = ${ary.map{it.toString()}.joinToString(" ")}") }
- 実行結果
ary = 2 3 5 7 11 ary = 3 5 7 11 2
ジェネリックスと拡張関数
[編集]拡張関数でもジェネリックス(型パラメーター)が使用可能です。
先の例は、Array<Int>とIntのアレイ専用でしたが、任意の型 T のアレイ Array<T> に拡張関数を拡張してみましょう。
- <T>Array<T>にrotate()を定義
fun main() { fun <T> Array<T>.rotate() { val t = this[0] var i = 1 while (i < this.size) { this[i - 1] = this[i] i++ } this[this.size - 1] = t } var ary = arrayOf(2, 3, 5, 7, 11) println("ary = ${ary.map{it.toString()}.joinToString(" ")}") ary.rotate() println("ary = ${ary.map{it.toString()}.joinToString(" ")}") var fary = Array(8){ val x = 1.0 * it; x * x } println("fary = ${fary.map{it.toString()}.joinToString(" ")}") fary.rotate() println("fary = ${fary.map{it.toString()}.joinToString(" ")}") var sary = arrayOf("A", "B", "C", "D", "E", "F") println("sary = ${sary.map{it.toString()}.joinToString(" ")}") sary.rotate() println("sary = ${sary.map{it.toString()}.joinToString(" ")}") }
- 実行結果
ary = 2 3 5 7 11 ary = 3 5 7 11 2 fary = 0.0 1.0 4.0 9.0 16.0 25.0 36.0 49.0 fary = 1.0 4.0 9.0 16.0 25.0 36.0 49.0 0.0 sary = A B C D E F sary = B C D E F A
拡張は静的に解決されます
[編集]拡張は、一見するとクラスの中に後からメンバー関数を追加しているように見えるかもしれませんが、インスタンス.メソッド(実引数リスト)
のパターンに合致する拡張定義があるかを静的に調べ、該当する拡張関数があればそれを呼出すことで実現しています。
このため拡張関数でメンバー関数をオーバーライドすることはできません。
クラス
[編集]Kotlinは関数型プログラミング言語であると同時に、クラスベースのオブジェクト指向プログラミング言語です。 クラス( class )はオブジェクトの設計図であり、インスタンスはその設計図に基づいて作成されます。
クラス定義
[編集]クラスは、キーワード class
を使って定義します。
- 空のクラスの定義とインスタンス化
class X // クラス定義 fun main() { val x = X() // インスタンス化 println("${x::class.simpleName}") // クラス名を表示 }
- 実行結果
X
- private なプロパティ s を持つクラスの定義
class X constructor(s: String) // クラス定義 fun main() { val x = X("abc") // インスタンス化 println("${x::class.simpleName}") // クラス名を表示 // x.s --- Unresolved reference: s ;; s は private なので、ここでは参照できません }
- 実行結果
X
class X constructor(s: String)
- この文脈でのソフト・キーワード constructor は、以下のように省略可能です。
class X(s: String)
- public でイミュータブルなプロパティ s を持つクラスの定義
class X(val s: String) // クラス定義 fun main() { val x = X("abc") // インスタンス化 println("${x::class.simpleName}") // クラス名を表示 println("x.s = ${x.s}") // プロパティーの値を表示 // x.s = "xyz" --- Val cannot be reassigned ;; イミュータブルなプロパティーは書換え不可 }
- 実行結果
X x.s = abc
- プロパティーの値の参照はできますが、
val
なので書換えはできません。
- public でミュータブルなプロパティ s を持つクラスの定義
class X(var s: String) // クラス定義 fun main() { val x = X("abc") // インスタンス化 println("${x::class.simpleName}") // クラス名を表示 println("x.s = ${x.s}") // プロパティーの値を表示 x.s = "xyz" // イミュータブルなプロパティーは値の書換えが可能 println("x.s = ${x.s}") // プロパティーの値を表示 }
- 実行結果
X x.s = abc x.s = xyz
- コンストラクターのパラメーターを
val
からval
に変更したので、ミュータブルとなりプロパティーの値を変更できるようになりました。
- init はコンストラクターの後に呼出されるブロック
class X(var s: String) { // クラス定義 init { println("init: s = ${s}") } } fun main() { val x = X("abc") // インスタンス化 println("${x::class.simpleName}") // クラス名を表示 println("x.s = ${x.s}") // プロパティーの値を表示 x.s = "xyz" // イミュータブルなプロパティーは値の書換えが可能 println("x.s = ${x.s}") // プロパティーの値を表示 }
- 実行結果
init: s = abc X x.s = abc x.s = xyz
- クラス定義冒頭のコンストラクターにはコードをかけないので init ブロックに書きます
クラスのメンバー
[編集]コンストラクター
[編集]Kotlinのクラスはプライマリーコンストラクターと1つまたは複数のセカンダリーコンストラクターを持つことができます。プライマリーコンストラクターはクラスヘッダの一部で、クラス名とオプションの型パラメターの後に続きます。
メンバー関数
[編集]メンバー関数は、クラス定義の中で定義された関数です[36]。 メンバー関数の呼出はドット記法で行います。 メンバー関数からは、プライベートなクラスのメンバーにアクセスできます。
プロパティ
[編集]オブジェクト
[編集]オブジェクトは、匿名クラスの定義とインスタンス化を同時に行うものです。
- オブジェクトの例
fun main() { var obj = object { var a = 3 var b = 4 override fun toString() = "($a, $b)" } println( """ obj => $obj obj.a => ${obj.a}, obj.b => ${obj.b} obj::class.simpleName => ${obj::class.simpleName} """ ) }
- 実行結果
obj => (3, 4) obj.a => 3, obj.b => 4 obj::class.simpleName => null
継承
[編集]クラスは、明示的に継承元を指定しない場合は Any
を継承します。
継承モディファイア
[編集]継承モディファイア( inheritance modifier )には、次のような種類があり、これらのトークンはモディファイア・キーワードです。
abstract
[編集]抽象クラス( abstract class )は、抽象メンバー関数( abstract method )だけを持つクラスです。
モディファイア・キーワード abstract
は、抽象メンバー関数の定義でも使われます。
final
[編集]ファイナルクラス( final class )は、継承を禁止したクラスです。
KotlinのクラスはJavaと異なり、ディフォルトで継承禁止なので、継承禁止を強調する意味しかありません。
モディファイア・キーワード final
は、オーバーライド禁止メンバーの宣言でも使われます。
open
[編集]オープンクラス( open class )は、継承を許可したクラスです。
KotlinのクラスはJavaと異なり、ディフォルトで継承禁止なので、継承を行う可能性がある場合は明示的に許可する必要があります。
モディファイア・キーワード open
は、メンバー関数のオーバーライド許可でも使われます。
これは、主にセキュリティー上の理由からで、クラスを継承することによりprotectedでの隠蔽に綻びが生じプログラムが「ハイジャック」されることの重要さに配慮したもので、 クラス設計に於いてのトレードオフ、「拡張性と頑強性」のディフォルトを頑強に振った言語設計になっています。
this
[編集]クラスのメンバー関数が、プロパティやメンバー関数を参照するとき、クラスの外の変数や関数との間で曖昧さが生じる事があります。
このようなときには、インスタンスを表すキーワード this
を使います[37]。
this
はこのほか、2 次コンストラクタから同じクラスの別のコンストラクタを呼出すときにもつかわれます。
- thisを使ったメンバー関数の限定
fun main() { fun printLine() = println("Function") class Simple { fun printLine() = println("Method") fun printLineNone() = printLine() fun printLineThis() = this.printLine() } val s = Simple() s.printLine() s.printLineNone() s.printLineThis() }
- 実行結果
Method Function Method
- メンバー関数から同じクラスのメンバー関数を呼出すとき、同名の関数があると関数が呼出されます。
- メンバー関数から同じクラスのメンバー関数を呼出すときには、同名の関数があると関数が呼出されます。
クラスモディファイア
[編集]クラスモディファイア( class modifier )には、次のような種類があり、これらのトークンはモディファイア・キーワードです。
enum
[編集]列挙型クラス( Enum class )は、有限個の識別子の集合を表現するために使用されます。
enum class DayOfWeek { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } fun main() { val today = DayOfWeek.MONDAY println("Today is $today") }
- 特性と用途
- 特定の値の集合を表現するために使用されます(例: 曜日、状態など)。
- 定数のような振る舞いを持ち、列挙型の各要素は固定された値として使用されます。
sealed
[編集]シールドクラス( Sealed class )は、制限されたクラス階層を表現するために使用されます。
sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String) : Result() } fun handleResult(result: Result) { when (result) { is Result.Success -> println("Success: ${result.data}") is Result.Error -> println("Error: ${result.message}") } } fun main() { val successResult = Result.Success("Data") val errorResult = Result.Error("An error occurred") handleResult(successResult) handleResult(errorResult) }
- 特性と用途
- 制限されたクラス階層を定義するために使用されます。サブクラスは通常、シールドクラス内にネストされます。
- シールドクラスのサブクラスは通常、特定の型の状態や結果を表現するために使用されます。
annotation
[編集]アノテーションクラス( annotation class )は、コードにメタデータを付加するための手段を提供します。
annotation class Author(val name: String) @Author("John Doe") class Book { // ブックの定義 } fun main() { val book = Book::class.java val authorAnnotation = book.getAnnotation(Author::class.java) val authorName = authorAnnotation?.name println("Author: $authorName") }
- 特性と用途
- コードに追加情報や設定を提供するために使用されます。
- ランタイムやコンパイル時にアノテーションを処理することで、特定の動作やコード生成をトリガーすることができます。
data
[編集]データクラス( Data class )は、データを保持するためのクラスで、copy() などのメンバー関数がクラスを定義しただけで生えてきます。
data class Person(val name: String, val age: Int) fun main() { val person = Person("Alice", 25) val copyPerson = person.copy(age = 30) println(person) println(copyPerson) }
- 特性と用途
- データのコンテナとして使用され、自動的にequals()、hashCode()、toString()、copy()メソッドが生成されます。
- イミュータブルなデータオブジェクトを簡潔に表現するために使用されます。
inner
[編集]インナークラス( Inner class )は、入れ子になった内側になったクラスが外側のクラスのメンバーにアクセスすることを可能にします。
class Outer { private val outerProperty = "Outer Property" inner class Inner { fun printOuterProperty() { println(outerProperty) } } } fun main() { val outer = Outer() val inner = outer.Inner() inner.printOuterProperty() }
- 特性と用途
- 外側のクラスのインスタンスに紐づく内側のクラスを定義するために使用されます。
- 内側のクラスは外側のクラスの非公開メンバーやプロパティにアクセスすることができます。
value
[編集]値クラス( Value class )は、イミュータブルなスカラー値の型を定義します。プロポーザル段階では inline class と呼ばれていました。
inline class Password(val value: String) fun main() { val password = Password("secret") println("Password: ${password.value}") }
- 特性と用途
- 単一の値を表すために使用されます。プリミティブ型のように振る舞い、ボックス化のオーバーヘッドを回避します。
+ ラッパークラスとして機能し、型安全性やコードの表現力を向上させます。
object
[編集]オブジェクト( object )は、モディファイア・キーワードではありませんが、特殊なクラスの一種なので併せて紹介します。 オブジェクトは一過的な匿名クラスを定義し、そのインスタンスの生成を行います。
object Logger { fun log(message: String) { println("Log: $message") } } fun main() { Logger.log("Hello, World!") }
- 特性と用途
- シングルトンパターンを実装するために使用されます。クラスの単一のインスタンスを表現し、グローバルなアクセスポイントとして機能します。
- 関連するメソッドやプロパティを含めることができ、特定の目的に特化したオブジェクトを表現することができます。
- まとめ
各クラスモディファイアは、異なる用途や特性を持っており、柔軟なクラス定義を可能にします。 適切なモディファイアを選択することで、コードの意図を明確に表現し、保守性や可読性を向上させることができます。
可視性モディファイア
[編集]クラス、オブジェクト、インターフェース、コンストラクター、関数、およびプロパティとそのセッターは、可視性モディファイア( Visibility modifiers )を持つことができます。ゲッターは常にそのプロパティと同じ可視性を持っています[38]。
可視性モディファイアには、次のような4種類があり、これらのトークンはモディファイア・キーワードです。
デフォルトの可視性はpublicです。
パッケージ
[編集]関数、プロパティ、クラス、オブジェクト、およびインタフェースは、パッケージの中で直接「トップレベル」で宣言することができます。
- 可視性モディファイアを使用しない場合、デフォルトではpublicが使用され、宣言はどこでも見えるようになります。
- 宣言に private を指定すると、その宣言を含むファイル内でのみ可視化されます。
- internalと指定した場合は、同じモジュール内であればどこでも見えるようになります。
- protected修飾子は、トップレベルの宣言には使えません。
クラスのメンバー
[編集]クラス内部で宣言されたメンバー。
- private は、そのメンバーがこのクラスの内部でのみ可視であることを意味します(そのクラスのすべてのメンバーを含む)。
- protected は、private とマークされたメンバーと同じ可視性を持ちますが、サブクラスでも可視化されることを意味します。
- internal は、宣言したクラスを見たこのモジュール内のクライアントが、その内部のメンバーを見ることができることを意味します。
- public は、宣言クラスを見たすべてのクライアントがその public メンバを見ることができることを意味します。
protectedまたはinternalメンバーをオーバーライド( override )し、可視性を明示的に指定しない場合、オーバーライドしたメンバーも元のメンバーと同じ可視性を持つことになります。
コンストラクター
[編集]クラスの一次コンストラクタの可視性を指定するには、次の構文を使用します。
class C private constructor(a: Int) { ... }
- ここでは、コンストラクターは private です。デフォルトでは、すべてのコンストラクターは public です。これは、クラスが見えるところならどこでもコンストラクターが見えるということです(裏を返せば、内部クラスのコンストラクターは同じモジュール内でしか見えないということです)。
ローカル宣言
[編集]ローカル変数、関数、クラスは可視性モディファイアを持つことができません。
モジュール
[編集]internal 可視性モディファイアは、そのメンバーが同じモジュール内で可視であることを意味します。具体的にモジュールとは、例えば、一緒にコンパイルされたKotlinファイルの集合のことです。
- IntelliJ IDEAモジュール。
- Mavenプロジェクト
- Gradleのソースセット(ただし、testのソースセットはmainの内部宣言にアクセスできる)。
- kotlinc Antタスクの1回の呼び出しでコンパイルされるファイル群。
抽象クラス
[編集]抽象クラス( abstract class )は、抽象メソッド( abstract method )だけを持つクラスであり、直接のインスタンス化ができません。抽象メソッドは本体を持たず、具体的な実装はそのサブクラスに委ねられます。Kotlinでは、abstract
キーワードを使用して抽象クラスと抽象メソッドを宣言します。
抽象クラスの宣言
[編集]abstract class Shape { abstract fun draw() } class Circle : Shape() { override fun draw() { println("Drawing a circle") } } class Rectangle : Shape() { override fun draw() { println("Drawing a rectangle") } }
上記の例では、Shape
という抽象クラスがあります。このクラスは抽象メソッド draw
を宣言しています。サブクラスである Circle
と Rectangle
は、それぞれ draw
メソッドをオーバーライドして具体的な実装を提供します。
抽象クラスのインスタンス化
[編集]fun main() { // コンパイルエラー: Cannot create an instance of an abstract class // val shape = Shape() val circle = Circle() circle.draw() val rectangle = Rectangle() rectangle.draw() }
抽象クラスは直接インスタンス化できないため、Shape
クラスのインスタンスを作成しようとするとコンパイルエラーが発生します。代わりに、具象サブクラスである Circle
や Rectangle
のインスタンスを作成して使用します。
抽象プロパティとコンストラクタ
[編集]抽象クラスは抽象プロパティを持つこともできます。また、抽象クラス自体はコンストラクタを持つことができます。
abstract class Shape(val name: String) { abstract fun draw() } class Circle(name: String) : Shape(name) { override fun draw() { println("Drawing a circle named $name") } } class Rectangle(name: String) : Shape(name) { override fun draw() { println("Drawing a rectangle named $name") } }
この例では、Shape
クラスはコンストラクタを持ち、name
という抽象プロパティを宣言しています。サブクラスである Circle
と Rectangle
は、コンストラクタで name
を渡し、draw
メソッドをオーバーライドしています。
抽象クラスは、クラス階層の一部として柔軟で再利用可能なコードを設計する際に役立ちます。
ユースケースとベストプラクティス
[編集]抽象クラスは、以下のようなユースケースやベストプラクティスに適しています。
- 部分的な実装の提供
- 抽象クラスは、一部のメソッドやプロパティの実装を提供することができます。これにより、サブクラスは抽象メソッドだけでなく、既存の実装を再利用できます。
abstract class Shape { fun commonMethod() { // 共通の実装 } abstract fun draw() }
- メソッドの強制
- 抽象クラスには抽象メソッドが含まれており、これをサブクラスで実装することが義務付けられます。これにより、サブクラスが特定のメソッドを実装することが保証されます。
abstract class Printer { abstract fun print() } class LaserPrinter : Printer() { override fun print() { // レーザープリンターの実装 } }
- 継承と拡張
- 抽象クラスを使用すると、クラス階層を構築し、新しい機能を追加していくことができます。これは、将来的に変更や拡張が発生する可能性がある場合に特に役立ちます。
abstract class Animal { abstract fun makeSound() } class Dog : Animal() { override fun makeSound() { println("Woof!") } } class Cat : Animal() { override fun makeSound() { println("Meow!") }
- コンストラクタとプロパティ
- 抽象クラスはコンストラクタを持ち、抽象プロパティを宣言できます。これにより、サブクラスが特定のプロパティを持つことが保証され、コンストラクタで初期化できます。
abstract class Vehicle(val model: String) { abstract fun start() abstract fun stop() } class Car(model: String) : Vehicle(model) { override fun start() { println("Starting the car") } override fun stop() { println("Stopping the car") } }
- 複数のインターフェースの代替
- 抽象クラスは複数のメソッドやプロパティをまとめて提供できるため、インターフェースが多すぎる場合に、抽象クラスを使用して階層を整理することができます。
abstract class UIControl { abstract fun render() abstract fun onClick() } class Button : UIControl() { override fun render() { println("Rendering button") } override fun onClick() { println("Button clicked") } }
抽象クラスは、柔軟性と再利用性を向上させるために使われますが、適切に設計される必要があります。必要以上に多くの機能を含めると、過度な依存関係が生まれる可能性があるため、慎重な設計が求められます。
ファイナルクラス
[編集]ファイナルクラス(final class
)は、Kotlinにおいて継承を禁止したクラスを指します。Kotlinでは、デフォルトでクラスが継承不可(final
)となっており、継承可能にするためには明示的にopen
修飾子を使用する必要があります。そのため、final
キーワードは主にオーバーライド禁止メンバーの宣言で使用され、クラス自体に適用することは少ないです。
ファイナルクラスの宣言
[編集]final class MyFinalClass { // クラスの定義 }
上記の例では、MyFinalClass
はファイナルクラスとして宣言されています。このクラスは他のクラスから継承できません。
オーバーライド禁止メンバーの宣言
[編集]open class MyBaseClass { final fun myFinalMethod() { // オーバーライド禁止のメソッド } }
上記の例では、MyBaseClass
の myFinalMethod
は final
修飾子が付いており、このメソッドはオーバーライドできません。
ファイナルクラスの利用例
[編集]final class Configuration { // クラスの定義 } class ApplicationSettings(config: Configuration) { // FinalClass を利用したクラスの定義 }
上記の例では、Configuration
クラスがファイナルクラスとして定義されています。このクラスは他のクラスから継承できないため、設定情報の不正な変更や上書きを防ぐことが期待されます。ApplicationSettings
クラスは Configuration
を利用してアプリケーションの設定を管理しています。
ファイナルクラスは特定の状況で利用され、継承を禁止してクラスの拡張を制御するために使用されます。
オープンクラス
[編集]オープンクラス(open class
)は、Kotlinにおいて継承を許可したクラスを指します。
Kotlinのクラスはデフォルトで継承不可(final
)となっているため、クラスを継承可能にするには明示的にopen
修飾子を使用する必要があります。
オープンクラスの宣言
[編集]open class MyOpenClass { // クラスの定義 }
上記の例では、MyOpenClass
はオープンクラスとして宣言されています。このクラスは他のクラスから継承できます。
メソッドのオーバーライド許可
[編集]open class MyBaseClass { open fun myMethod() { // オーバーライド可能なメソッド } }
上記の例では、MyBaseClass
の myMethod
は open
修飾子が付いており、このメソッドはオーバーライド可能です。
オープンクラスの利用例
[編集]open class Shape { open fun draw() { println("Drawing a shape") } } class Circle : Shape() { override fun draw() { println("Drawing a circle") } } class Square : Shape() { override fun draw() { println("Drawing a square") } }
上記の例では、Shape
クラスがオープンクラスとして定義されています。Circle
と Square
クラスが Shape
を継承し、draw
メソッドをオーバーライドしています。これにより、各図形クラスは自身の特定の描画方法を持つことができます。
オープンクラスは継承を許可し、クラスの拡張やカスタマイズが可能となります。ただし、使用する際には慎重に設計し、必要なメソッドやプロパティに対してのみopen
修飾子を使用することが推奨されます。
列挙型クラス
[編集]列挙型クラス( Enum classes )は、キーワード enum
を class
に前置して定義します[39]。
Swift/オブジェクト指向#列挙型の例を Kotlin 向けにモディファイしました。
- 列挙型クラスとメソッド
enum class Azimuth { North, South, East, West; override fun toString() = when (this) { Azimuth.North -> "北" Azimuth.South -> "南" Azimuth.East -> "東" Azimuth.West -> "西" } fun deg() = when (this) { Azimuth.North -> 0 * 90 Azimuth.South -> 2 * 90 Azimuth.East -> 1 * 90 Azimuth.West -> 3 * 90 } } fun main() { val n = Azimuth.North println("n => $n, az.deg() => ${n.deg()}") println("------------------------------------") for (az in Azimuth.values()) { println("as.name =>${az.name}, as => $az, az.deg() => ${az.deg()}") } println(enumValues<Azimuth>().joinToString{it.name}) }
- 実行結果
n => 北, az.deg() => 0 ------------------------------------ as.name =>North, as => 北, az.deg() => 0 as.name =>South, as => 南, az.deg() => 180 as.name =>East, as => 東, az.deg() => 90 as.name =>West, as => 西, az.deg() => 270 North, South, East, West
- override fun toString() でEnumのディフォルトの文字列化メソッドをオーバーライドしています。
シールドクラス
[編集]シールドクラス(Sealed class
)は、制限されたクラス階層を表現するために使用される Kotlin 特有の機能です。シールドクラスは、特定のサブクラスのみを許容し、新しいサブクラスの追加を防ぎます。これにより、特定の型に対するパターンマッチングが容易になります。
シールドクラスの宣言
[編集]sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String) : Result() object Loading : Result() }
上記の例では、Result
クラスはシールドクラスとして宣言されています。このクラスには3つのサブクラスがあります:Success
、Error
、および Loading
。Success
と Error
はデータクラスで、それぞれデータを保持します。Loading
はオブジェクトクラスで、データを保持しません。
シールドクラスの利用例
[編集]fun processResult(result: Result) { when (result) { is Result.Success -> println("Success: ${result.data}") is Result.Error -> println("Error: ${result.message}") Result.Loading -> println("Loading...") } }
上記の例では、Result
クラスのサブクラスごとに異なる処理を行う processResult
関数があります。when
式を使用して、特定のサブクラスに対するパターンマッチングを行っています。
利点
[編集]- コンパイラがすべてのサブクラスを知っているため、
when
式などのパターンマッチングが網羅的であることを確認できます。 - 新しいサブクラスが追加された場合、コンパイラは未処理のケースがあるかどうかを検知し、警告を発生させます。
制限
[編集]- シールドクラスは同じファイル内で宣言されたクラスに対してしか有効ではありません。
シールドクラスは、特に固定されたクラス階層を表現する場合や、特定の型に対する安全なパターンマッチングを行う場合に便利です。
アノテーションクラス
[編集]アノテーションクラス(annotation class
)は、Kotlinにおいてコードにメタデータを付加するための手段を提供します。これにより、コンパイラや実行時の処理で追加の情報を提供することが可能となります。
アノテーションクラスの宣言
[編集]アノテーションクラスは、@
シンボルを使って宣言されます。アノテーションクラスの主な目的は、アノテーションを作成し、それをコードの要素に適用することです。
annotation class MyAnnotation(val name: String, val version: Int)
上記の例では、MyAnnotation
というアノテーションクラスが宣言されています。このアノテーションは、name
と version
というパラメータを持っています。
アノテーションの利用
[編集]アノテーションは、クラスや関数、プロパティなど、さまざまな要素に適用できます。
@MyAnnotation(name = "MyApp", version = 1) class MyClass { @MyAnnotation(name = "MyFunction", version = 2) fun myFunction() { // 関数の本体 } }
上記の例では、MyClass
クラスとその中の myFunction
関数に MyAnnotation
が適用されています。これにより、このクラスや関数に対する追加のメタデータが提供されます。
アノテーションのプロセッシング
[編集]アノテーションをプロセッシングするためには、リフレクションやKotlinのアノテーションプロセッサを使用することがあります。これにより、アノテーションに関連する処理を行うカスタムロジックを実装できます。
// アノテーションプロセッサの例 fun processMyAnnotation(element: AnnotatedElement) { val myAnnotation = element.getAnnotation(MyAnnotation::class.java) if (myAnnotation != null) { println("Name: ${myAnnotation.name}, Version: ${myAnnotation.version}") } }
上記の例では、AnnotatedElement
を受け取り、その要素に MyAnnotation
が適用されているかを調べ、情報を取得しています。
アノテーションクラスは、コードにメタデータを追加し、プロセッシングや設定などの目的で使用されます。
データクラス
[編集]データクラス(Data class
)は、Kotlinにおいてデータの保持や操作を目的としたクラスで、copy()
メソッドなどがクラスを定義するだけで自動的に生成されます。これにより、不変性やイミュータビリティ(immutable)を維持しながら、簡潔で効果的なデータクラスを作成できます。
データクラスの宣言
[編集]data class Person(val name: String, val age: Int)
上記の例では、Person
クラスがデータクラスとして宣言されています。コンストラクタで定義されたプロパティ(name
と age
)に対して、equals()
、hashCode()
、toString()
などの標準メソッドが自動的に生成されます。
データクラスの利用
[編集]val person1 = Person("Alice", 30) val person2 = Person("Alice", 30) // equals() メソッドにより、プロパティの内容が一致するか比較 println(person1 == person2) // true // toString() メソッドにより、クラスの内容を文字列として表示 println(person1.toString()) // Person(name=Alice, age=30) // copy() メソッドにより、一部のプロパティを変更した新しいインスタンスを作成 val modifiedPerson = person1.copy(age = 31) println(modifiedPerson) // Person(name=Alice, age=31)
上記の例では、equals()
メソッドにより person1
と person2
の内容が一致するか比較され、toString()
メソッドによりクラスの内容が文字列として表示されます。また、copy()
メソッドを使って一部のプロパティを変更した新しいインスタンスを作成しています。
データクラスの生成されるメソッド
[編集]データクラスが生成される主なメソッドは以下の通りです。
equals()
: プロパティごとの内容比較を行います。hashCode()
: プロパティごとにハッシュコードを生成します。toString()
: クラスの内容を文字列として返します。copy()
: インスタンスのコピーを作成します。一部のプロパティを変更できます。
データクラスは、イミュータブルでありながら効果的にデータを操作するための便利な手段を提供します。
インターフェース
[編集]Kotlinのインターフェース(interface
)は、抽象的なメソッドの宣言と、メソッドの実装を含むことができます。インターフェースは、抽象クラスと異なり状態を保持することができません。プロパティを持つ場合、これらは抽象クラスであるか、アクセサーの実装を提供する必要があります[40]。
インターフェースの宣言
[編集]interface MyInterface { fun doSomething() // 抽象メソッドの宣言 fun doSomethingElse() { // デフォルトのメソッド実装 println("Default implementation of doSomethingElse") } val property: Int // 抽象プロパティの宣言 }
上記の例では、MyInterface
インターフェースが、抽象メソッド doSomething()
とデフォルトのメソッド doSomethingElse()
、抽象プロパティ property
を宣言しています。
インターフェースの実装
[編集]class MyClass : MyInterface { override fun doSomething() { // メソッドの実装 println("Implementation of doSomething") } override val property: Int get() = 42 // プロパティの実装 }
上記の例では、MyClass
クラスが MyInterface
インターフェースを実装しています。doSomething()
メソッドと property
プロパティをオーバーライドしています。
インターフェースの複数準拠
[編集]interface A { fun methodA() } interface B { fun methodB() } class C : A, B { override fun methodA() { // A インターフェースの実装 } override fun methodB() { // B インターフェースの実装 } }
上記の例では、C
クラスが A
インターフェースと B
インターフェースの両方を実装しています。複数のインターフェースをカンマで区切って指定することができます。
インターフェースの継承
[編集]interface D : A, B { fun methodD() }
上記の例では、D
インターフェースが A
インターフェースと B
インターフェースを継承しています。これにより、D
インターフェースは A
と B
のメソッドを含むことになります。
Kotlinのインターフェースは、クラスが異なる振る舞いを持つ場合に簡潔で柔軟な解決策を提供します。異なるインターフェースを実装することで、複数の振る舞いを同じクラスで組み合わせることができます。
インナークラス
[編集]インナークラス(Inner class
)は、入れ子になった内側のクラスが外側のクラスのメンバーにアクセスすることを可能にします。これにより、外側のクラスと強い結びつきを持ちながら、内部で独自の振る舞いやデータを持つクラスを定義できます。
インナークラスの宣言
[編集]class OuterClass {
private val outerProperty: Int = 10
inner class InnerClass {
fun printOuterProperty() {
// OuterClass のプロパティにアクセス
println("Outer property: $outerProperty")
}
}
}
上記の例では、OuterClass
という外側のクラスがあり、その中に InnerClass
というインナークラスが定義されています。InnerClass
は OuterClass
のプロパティにアクセスすることができます。
インナークラスの利用
[編集]fun main() {
val outerInstance = OuterClass()
val innerInstance = outerInstance.InnerClass()
innerInstance.printOuterProperty()
}
上記の例では、OuterClass
のインスタンスを作成し、そのインナークラスである InnerClass
のインスタンスを取得しています。そして、InnerClass
のメソッドを呼び出して外側のクラスのプロパティにアクセスしています。
インナークラスの特徴
[編集]- 外部クラスへのアクセス: インナークラスは、外部クラスのメンバーにアクセスできます。外部クラスのプロパティやメソッドに直接アクセスできるため、疎結合な設計が可能です。
- thisキーワードの挙動: インナークラスでは、外部クラスのインスタンスにアクセスするために
this@OuterClass
のように明示的な指定ができます。これにより、外部クラスのメンバーと同じ名前のメンバーがインナークラス内にある場合に区別できます。 - 非静的なクラス: インナークラスは外部クラスのインスタンスに依存しており、非静的なクラスです。したがって、外部クラスのインスタンスが存在しないとインナークラスも存在しません。
インナークラスは、外部クラスとの緊密な連携が必要な場合や、外部クラスのプライベートなメンバーにアクセスする必要がある場合に有用です。
値クラス
[編集]値クラス(data class
)は、イミュータブルでスカラーな値の型を定義するための概念です。プロポーザル段階では inline class
と呼ばれていました。値クラスは、プリミティブ型のように振る舞い、同時に型安全性を提供します。
値クラスの宣言
[編集]data class Email(val value: String)
上記の例では、Email
という値クラスが宣言されています。この値クラスは、イミュータブルであり、value
というプロパティを持っています。
値クラスの利用
[編集]fun main() {
val email1 = Email("john@example.com")
val email2 = Email("jane@example.com")
println(email1 == email2) // 値クラスの比較
}
上記の例では、異なるインスタンスの Email
クラスを作成し、その値が等しいかを比較しています。値クラスでは、プライマリコンストラクタの引数で値が確定し、その値に基づいて等価性が判定されます。
値クラスの特徴
[編集]- イミュータブル性: 値クラスは不変(イミュータブル)であるため、一度生成されたインスタンスの値は変更されません。これにより、安全で予測可能なコードを実現します。
- 型安全性: 値クラスは型安全性を提供します。異なる値クラス間は厳密に区別され、混同されることがありません。
- 自動生成メソッド: 値クラスは
equals()
やhashCode()
などのメソッドを自動生成します。これにより、等価性の確認やコレクションの利用が容易になります。 - ボクシングの回避: 値クラスはプリミティブ型のように振る舞い、一部のボクシングを回避します。これにより、メモリ効率が向上します。
値クラスは、ドメインモデリングや特定のデータ型を表現する際に有用であり、プロジェクト全体でのコードの理解性や保守性を向上させる役割を果たします。
オブジェクト
[編集]オブジェクト(object
)は、モディファイア・キーワードではありませんが、特殊なクラスの一種であり、一過的な匿名クラスを定義し、その唯一のインスタンスを生成します。オブジェクトはシングルトンのような振る舞いを持ち、一般的には特定の目的に使用されます。
オブジェクトの宣言
[編集]object Logger {
fun log(message: String) {
println("Log: $message")
}
}
上記の例では、Logger
という名前のオブジェクトが宣言されています。このオブジェクトはシングルトンであり、log
メソッドを持っています。
オブジェクトの利用
[編集]fun main() {
Logger.log("This is a log message.")
}
上記の例では、Logger
オブジェクトの唯一のインスタンスにアクセスし、その log
メソッドを呼び出しています。オブジェクトは初回のアクセス時に遅延初期化され、以降は同じインスタンスが再利用されます。
オブジェクトの特徴
[編集]- シングルトンパターン: オブジェクトはシングルトンとして振る舞います。クラスの唯一のインスタンスを持ち、それにアクセスするために新たなインスタンスを生成することはありません。
- 遅延初期化: オブジェクトは初回のアクセス時に初期化されます。これにより、プログラムが実際に必要なときに初期化処理が行われます。
- クラスメンバー: オブジェクトはメソッドやプロパティを持つことができます。これらは通常のクラスメンバーと同じようにアクセスできます。
- 継承不可: オブジェクトは継承できません。そのため、他のクラスがこのオブジェクトを継承することはできません。
オブジェクトは特定のタスクや目的に対して唯一のインスタンスを提供する場合に利用され、例えばロギングや設定の管理などに適しています。
ジェネリックス
[編集]ジェネリックス( Generics )とは、汎用的なクラス・関数やメソッドを特定の型に対応づける機能のことです[41]。
キーワード一覧
[編集]Kotlinのキーワード( Keywords )は
に分類されます[42]。
ハード・キーワード
[編集]以下のトークンは、常にキーワードとして解釈され、識別子として使用することはできません[43]。 このようなキーワードをハード・キーワード( Hard keywords )と呼びます。
as
- 型キャスト( type casts )に使用されます。
- インポートの別名の指定に使用されます。
as?
安全なタイプキャスト( safe type casts )に使用されます。break
ループの実行を終了させます。class
クラスを宣言します。continue
最も近いループの次のステップに進みます。do
do/whileループ(条件式を後置するつループ)を開始します。else
条件が偽のときに実行されるif式の分岐を定義します。false
Boolean型の「偽」の値を表します。for
forループを開始します。fun
関数を宣言します。if
if式の先頭です。in
- forループで反復されるオブジェクトを指定します。
- 値が範囲、コレクション、または「contains」メソッドを定義している他のエンティティに属しているかどうかを確認するための中置演算子として使用されます。
- 同じ目的のためにwhen式で使用されます。
- 型パラメータをcontravariantとしてマークします。
!in
- 値が範囲、コレクション、または 'contains' メソッドを定義する他のエンティティに属さないことを確認する演算子として使用されます。
- 同じ目的のためにwhen式で使用されます。
!in
で1つのトークンなので、! と in の間に空白を挟むことはできません。
interface
インターフェース( interfaces )を宣言します。is
- 値が特定の型を持つかどうかをチェックします。
- 同じ目的のwhen式で使用されます。
!is
- 値が特定の型を持っていないかどうかをチェックします。
- 同じ目的のwhen式で使用されます。
!is
で1つのトークンなので、! と is の間に空白を挟むことはできません。
null
どのオブジェクトも指していないオブジェクト参照を表す定数です。object
クラスとそのインスタンスを同時に宣言します。package
現在のファイルのパッケージを指定します。return
最も近い包含関数または無名関数からの呼出し元に戻ります。super
- メソッドやプロパティのスーパークラス実装を参照します。
- 二次コンストラクタ( secondary constructor )からスーパークラスのコンストラクタを呼び出します。
this
- 現在のレシーバを指します。
- 2 次コンストラクタから同じクラスの別のコンストラクタを呼び出します。
throw
例外を投げます。true
Boolean型の「真」の値を表します。try
例外処理ブロックを開始します。typealias
型の別名を宣言します。typeof
将来の使用のために予約されています。val
読取り専用のプロパティまたはローカル変数を宣言します。var
変更可能なプロパティまたはローカル変数を宣言します。when
when式を開始します(与えられた分岐のうち1つを実行します)。while
while ループ(条件式を前置するループ)を開始します。
ソフト・キーワード
[編集]以下のトークンは、それが適用される文脈( context )ではキーワードとして機能し、他の文脈では識別子として使用することができます[44]。 このようなキーワードをソフト・キーワード( Soft keywords )と呼びます。
by
- インターフェース( interface )の実装( implementation )を別のオブジェクトに委譲( delegates )します。
- プロパティーのアクセッサー( accessors )の実装を別のオブジェクトに委譲します。
catch
特定の例外タイプ( specific exception type )を処理( handles )するブロックを開始します。constructor
一次または二次コンストラクタを宣言します。delegate
アノテーション使用側ターゲット[仮訳]( annotation use-site target )として使用されます。dynamic
Kotlin/JS コードで動的な型を参照します。field
アノテーション使用側ターゲット[仮訳]として使用されます。file
アノテーション使用側ターゲット[仮訳]として使用されます。finally
tryブロックが終了したときに必ず実行されるブロックを開始します。get
- プロパティのゲッター( getter )を宣言します。
- アノテーション使用側ターゲット[仮訳]として使用されます。
import
他のパッケージから現在のファイルに宣言をインポートします。init
イニシャライザーブロックを開始します。param
アノテーション使用側ターゲット[仮訳]として使用されます。property
アノテーション使用側ターゲット[仮訳]として使用されます。receiver
アノテーション使用側ターゲット[仮訳]として使用されます。set
- プロパティのセッター( setter )を宣言します。
- アノテーション使用側ターゲット[仮訳]として使用されます。
setparam
アノテーション使用側ターゲット[仮訳]として使用されます。value
キーワードclass
を付けてインラインクラス( inline class )を宣言します。where
汎用型パラメーター( a generic type parameter )の制約( constraints )を指定します。
アノテーション使用側ターゲット
[編集]アノテーション使用側ターゲット( Annotation use-site target )は、Kotlinにおいてアノテーションが特定の要素に対して適用される位置を指定する構文です。これにより、生成されるJavaバイトコードにおいてアノテーションがどの要素に適用されるかを正確に指定できます[45]。
アノテーション使用側ターゲットの指定
[編集]以下は、一般的なアノテーション使用側ターゲットの指定方法です。
- プロパティにアノテーションを指定する場合:
class Example { @get:MyAnnotation val myProperty: Int = 42 }
- この例では、
@get:MyAnnotation
と指定することで、プロパティのgetterメソッドに対してアノテーションを指定しています。
- 1次コンストラクタのパラメータにアノテーションを指定する場合:
class Example(@param:MyAnnotation val value: String)
- この例では、
@param:MyAnnotation
と指定することで、1次コンストラクタのパラメータに対してアノテーションを指定しています。
よく使われるアノテーション使用側ターゲット
[編集]@get
: プロパティのgetterメソッドに対してアノテーションを指定する。@set
: プロパティのsetterメソッドに対してアノテーションを指定する。@field
: プロパティ自体に対してアノテーションを指定する。
これらの使用側ターゲットを使うことで、アノテーションが生成される場所を明確に指定することができ、柔軟性を保ちつつ、正確な挙動を得ることができます。
モディファイア・キーワード
[編集]以下のトークンは、宣言の修飾語リスト( modifier lists of declarations )のキーワードとして機能し、他のコンテキストでは識別子として使用することができます[46]。 このようなキーワードをモディファイア・キーワード( Modifier keywords )と呼びます。
abstract
クラスやメンバが抽象的( as abstract )であることを表します。actual
マルチプラットフォーム・プロジェクト( multiplatform projects )におけるプラットフォーム固有の実装( platform-specific implementation )を意味します。annotation
アノテーションクラス( an annotation class )を宣言します。companion
コンパニオンオブジェクト( a companion object )を宣言します。const
プロパティをコンパイル時の定数( a compile-time constant )としてマークします。crossinline
インライン関数に渡されるラムダで、ローカルでない返り値を禁止します。data
クラスの正規メンバー( canonical members )を生成するようにコンパイラーに指示します。enum
列挙型( an enumeration )を宣言します。expect
宣言がプラットフォーム固有( as platform-specific )のものであり、プラットフォーム・モジュール(platform modules )で実装されることを期待するものとしてマークします。external
Kotlin の外部で実装される宣言であることを示します(JNI または JavaScript でアクセス可能)。final
メンバーのオーバーライドを禁止します。infix
中置記法( infix notation )で関数を呼び出すことを許可します。inline
関数とその関数に渡されたラムダを呼出し先でインライン化することをコンパイラに指示します。inner
ネストされたクラスから外側のクラスのインスタンスを参照できるようにします。internal
現在のモジュールで可視( as visible )となる宣言をマークします。lateinit
コンストラクターの外部で非 null プロパティを初期化します。noinline
インライン関数に渡されたラムダをインライン化しないようにします。open
クラスのサブクラス化またはメンバーのオーバーライドを許可します。operator
関数が演算子をオーバーロードしているか、または規約を実装( implementing a convention )しているかをマークします。out
型パラメータを共変( covariant )としてマークします。override
スーパークラスのメンバーのオーバーライドとしてメンバーにマークを付けます。private
現在のクラスまたはファイル内で宣言が可視化( as visible )されるようにマークします。protected
現在のクラスとそのサブクラスで宣言が可視化されるようにマークします。public
宣言がどこでも可視化されるようにマークします。reified
インライン関数の型パラメーター( type parameter )を、実行時にアクセス可能なものとしてマークします。sealed
シールされたクラス( sealed class ; サブクラス化が制限されたクラス)を宣言します。suspend
関数やラムダをサスペンド(コルーチンとして使用可能)するようにマークします。tailrec
関数を末尾再帰としてマークします (コンパイラーが再帰を反復に置換えることができます)。vararg
パラメーターに可変数の引数を渡せるようにします。
特殊識別子
[編集]以下の識別子は,コンパイラーが特定の文脈で定義したもので、他の文脈では通常の識別子として使用することができます[47]。 このような識別子を特殊識別子( Special identifiers )と呼びます。
field
プロパティーアクセサー( a property accessor )の内部で、プロパティーのバッキングフィールドを参照するために使用します。it
ラムダの内部で暗黙のうちにパラメーターを参照するために使用されます。
演算子と特殊シンボル
[編集]Kotlinは以下の演算子( Operators )や特殊特殊シンボル( special symbols )をサポートしています[48]。
+
,-
,*
,/
,%
— 算術演算子*
は、vararg パラメーターに配列を渡す場合にも使用されます。
=
- 代入演算子
- パラメーターのデフォルト値を指定するために使用されます。
+=
,-=
,*=
,/=
,%=
— 拡張された代入演算子。++
,--
— インクリメントおよびデクリメント演算子&&
,||
,!
— 論理 'and', 'or', 'not' 演算子 (ビット演算には、対応する infix 関数を使用してください)。==
,!=
— 等号演算子 (非プリミティブ型では equals() に変換される)。===
,!==
— 参照系等号演算子( referential equality operators )<
,>
,<=
,>=
— 比較演算子 (非プリミティブ型に対する compareTo() の呼出しに変換されます)[
,]
— インデックス付きアクセス演算子(getとsetの呼び出しに変換されます)!!
式が非nullであることを保証します。?.
安全な呼び出しを行います(レシーバーが非NULLの場合、メソッドを呼び出したり、プロパティにアクセスしたりします)。?:
左辺の値がNULLの場合、右辺の値を取ります(エルビス演算子)。::
メンバー参照またはクラス参照を作成します。..
範囲( a range )を生成します。:
宣言の中で、名前と型を分離します。?
型をnull可能( as nullable )であるとマークします。->
- ラムダ式のパラメーターと本体を分離します。
- 関数型のパラメーターと戻値の型宣言を分離します。
- when式の条件分岐と本体を分離します。
@
- アノテーションを導入します。
- ループラベルを導入または参照します。
- ラムダ・ラベルを導入または参照します。
- 外部スコープから 'this' 式を参照します。
- 外部のスーパークラスを参照します。
;
同じ行にある複数のステートメントを区切ります。$
文字列テンプレート内で変数または式を参照します。_
- ラムダ式で使用しないパラメーターを置換えます。
- 構造化宣言の未使用のパラメーターを代入します。
Kotlin標準ライブラリー
[編集]Kotlin標準ライブラリー( Kotlin Standard Library )は、以下のような機能を提供します[49]。
- 慣用的なパターン(let、apply、use、synchronizedなど)を実装した高階関数。
- コレクション(eager)やシーケンス(lazy)に対するクエリ操作を提供する拡張関数。
- 文字列や文字列列を扱うための各種ユーティリティ
- ファイル、IO、スレッドを便利に扱うための JDK クラスの拡張。
Kotlin標準ライブラリーはKotlin自身で書かれています。
- ディフォルトインポートされるパッケージ
アノテーション
[編集]kotlin.annotation.*
コレクション
[編集]Kotlinには、Arrayから始まり、Iterable, Collection, List, Set, Mapなどのコレクション型が豊富に用意されています。これらは主に kotlin.collections
パッケージで提供されており、ディフォルトインポートなので追加のインポートなしで利用できます[50]。
主要なパッケージとその機能の一覧 パッケージ 機能 Iterable 要素の反復処理をサポート Collection 要素のコレクションを表現 List 要素数が固定で、要素の値を変更不可 ArrayList 可変長の動的な配列 MutableList 要素の変更が可能なリスト Set 要素の重複を許さない Map キーと値のペアを保持する MutableMap 要素の変更が可能なMap LinkedHashSet 要素の挿入順を保持するSet LinkedHashMap 要素の挿入順を保持するMap
Iterable
[編集]Iterable
インターフェースは、Kotlinの標準ライブラリで使用される反復可能なコレクションの基本的なインターフェースです。このインターフェースは、コレクションが要素の反復処理を可能にするために必要な機能を提供します。
以下に、Iterable
インターフェースの主な要素と使用法を示します:
iterator()
メソッド:iterator
メソッドは、コレクション内の要素を反復処理するためのイテレータを返します。- イテレータは、
hasNext()
メソッドで次の要素の有無を確認し、next()
メソッドで次の要素を取得します。 - 通常、
for
ループやforEach
関数を使用して、このメソッドを呼び出すことなくコレクションを反復処理できます。
val iterable: Iterable<Int> = listOf(1, 2, 3, 4, 5) // for ループを使用した反復処理 for (element in iterable) { println(element) } // forEach 関数を使用した反復処理 iterable.forEach { element -> println(element) }
forEach
拡張関数:Iterable
インターフェースには、forEach
という拡張関数があります。これはラムダ式を使用して各要素に対する処理を行います。
val iterable: Iterable<Int> = listOf(1, 2, 3, 4, 5) // forEach 関数を使用した反復処理 iterable.forEach { element -> println(element) }
- 使用例:
Iterable
は多くの Kotlin のコレクションで使用されます。例えば、List
、Set
、Map
などがIterable
インターフェースを実装しています。Iterable
を実装することで、コレクションが要素の反復処理をサポートし、for
ループやforEach
関数などで簡単に使用できます。
// List は Iterable インターフェースを実装している val list: Iterable<Int> = listOf(1, 2, 3, 4, 5) // for ループを使用した反復処理 for (element in list) { println(element) }
Iterable
インターフェースは、Kotlinでコレクションを扱う際に非常に重要であり、様々な反復処理操作を可能にします。
Collection
[編集]Collection
インターフェースは、Kotlinの標準ライブラリで提供されるコレクション型の基本インターフェースの一つです。このインターフェースは、複数の要素を保持するデータ構造を表現し、それらの要素に対する基本的な操作を提供します。
以下に、Collection
インターフェースの主な特徴と使用法を示します:
- 要素の追加と削除:
Collection
インターフェースは、要素の追加や削除といった基本的な操作を提供します。add(element: E)
メソッドを使用して要素を追加し、remove(element: E)
メソッドを使用して指定した要素を削除します。
val collection: Collection<Int> = listOf(1, 2, 3, 4, 5) // 要素の追加はサポートされない // collection.add(6) // エラー // 要素の削除 val modifiedCollection = collection - 3 println(modifiedCollection) // [1, 2, 4, 5]
- 要素の存在確認:
contains(element: E)
メソッドを使用して、指定した要素がコレクション内に存在するかどうかを確認できます。
val collection: Collection<Int> = listOf(1, 2, 3, 4, 5) // 要素の存在確認 val containsThree = collection.contains(3) println(containsThree) // true
- サイズの取得:
size
プロパティを使用して、コレクション内の要素の総数を取得できます。
val collection: Collection<Int> = listOf(1, 2, 3, 4, 5) // コレクションのサイズ取得 val size = collection.size println(size) // 5
- 反復処理:
Iterable
インターフェースを継承しているため、forEach
関数などを使用してコレクション内の要素を反復処理できます。
val collection: Collection<Int> = listOf(1, 2, 3, 4, 5) // forEach 関数を使用した反復処理 collection.forEach { element -> println(element) }
- 他のコレクションとの操作:
Collection
インターフェースを実装するコレクションは、他のコレクションとの操作を行うための便利な関数を提供します。例えば、和集合、差集合、共通要素などを計算できます。
val collection1: Collection<Int> = listOf(1, 2, 3, 4, 5) val collection2: Collection<Int> = listOf(4, 5, 6, 7, 8) // 和集合 val unionResult = collection1 union collection2 println(unionResult) // [1, 2, 3, 4, 5, 6, 7, 8] // 差集合 val subtractResult = collection1 subtract collection2 println(subtractResult) // [1, 2, 3] // 共通要素 val intersectResult = collection1 intersect collection2 println(intersectResult) // [4, 5]
Collection
インターフェースは、Kotlinのコレクションの基本的な機能を提供し、様々な種類のコレクションがこれを実装しています。これにより、異なる種類のコレクションを一貫して扱うことができます。
List
[編集]Listは、要素数が固定で要素の値を変更できないコレクションです。
List()
[編集]- List()を使ったListの生成
fun main() { val list = List(5){it} println("list::class.simpleName ⇒ ${list::class.simpleName}") println("list[0]::class.simpleName ⇒ ${list[0]::class.simpleName}") list.forEach{print("$it ")} println("") val list2 = List(5){(it*it).toString()} println("list2[0]::class.simpleName ⇒ ${list2[0]::class.simpleName}") list2.forEach{print("$it ")} println("") }
- 実行結果
list::class.simpleName ⇒ ArrayList list[0]::class.simpleName ⇒ Int 0 1 2 3 4 list2[0]::class.simpleName ⇒ String 0 1 4 9 16
- List()はListのコンストラクターで、引数として要素数をとり、ブロックが初期化式になります。
listOf
[編集]- listOf()を使ったListの生成
fun main() { val list = listOf(1, 9, 3, 5, 23, 1) println("${list::class.simpleName}") println("${list[0]::class.simpleName}") for (s in list) if (s > 10) break else print("$s ") println("") run { list.forEach{ if (it > 10) return@run else print("$it ") } } println("") }
- 実行結果
ArrayList Int 1 9 3 5 1 9 3 5
- ListOf()は可変長引数の関数で、引数が生成されるListの要素になります。
- Listの要素の型は、型強制できる最小公倍数的な方になります(例えば Int と Long が混在していたら Long)。
ArrayList
[編集]fun main() { // ArrayListを使ったListの生成 val arrayList = ArrayList<String>() arrayList.add("Kotlin") arrayList.add("Java") arrayList.add("Python") println("${arrayList::class.simpleName}") for (lang in arrayList) { println(lang) } }
- 実行結果
ArrayList Kotlin Java Python
上記の例では、ArrayList
を使ってListを生成しています。ArrayListは要素の順序が保持され、可変長の動的な配列を表現します。add
メソッドを使用して要素を追加することができます。
MutableList
[編集]fun main() {
// MutableListを使ったListの生成
val mutableList = mutableListOf(1, 2, 3, 4, 5)
mutableList.add(6)
println("${mutableList::class.simpleName}")
for (num in mutableList) {
print("$num ")
}
println("")
}
- 実行結果
ArrayList 1 2 3 4 5 6
mutableListOf()
を使って MutableList
を生成し、add
メソッドを使用して要素を追加しています。MutableListは要素の変更が可能なListです。
これらの例から分かるように、Kotlinのコレクションは型安全で、読み取り専用と可変なコレクションの違いが明確になっています。リストの他にも、SetやMapも用意されており、それぞれの特徴に応じて適切なものを選択できます。
ユースケース
[編集]kotlin.collections
パッケージのコレクションクラスや関連機能は、さまざまなプログラミングシナリオで利用されます。以下は、kotlin.collections
の主なユースケースのいくつかです:
- データの格納と操作: List、Set、Mapなどのコレクションは、データを効果的に格納し、操作するために使用されます。例えば、リストは順序つきのデータの集合を表現し、セットは一意な要素の集合を表現します。
- 反復処理とフィルタリング: Iterable インターフェースを使用してコレクションを反復処理し、必要なデータを抽出することができます。これは、フィルタリングや変換などの操作に役立ちます。
- 不変性と可変性の管理: List と MutableList、Set と MutableSet、Map と MutableMap など、各コレクションには不変なバージョンと可変なバージョンがあります。これにより、不変性を保ちながら必要に応じてデータを変更できます。
- 関数型プログラミング: コレクション操作には関数型プログラミングのアプローチがあり、
filter
、map
、reduce
などの関数を使用してデータを処理できます。
以下は、これらのユースケースの簡単な例です:
// データの格納と操作
val list = listOf(1, 2, 3, 4, 5)
val set = setOf(1, 2, 3, 4, 5)
val map = mapOf(1 to "one", 2 to "two", 3 to "three")
// 反復処理とフィルタリング
val filteredList = list.filter { it > 2 }
// 不変性と可変性の管理
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4)
// 関数型プログラミング
val squaredValues = list.map { it * it }
val sum = list.reduce { acc, value -> acc + value }
これらの例は一部の機能を示すものであり、kotlin.collections
のコレクションは、さまざまなプログラミングニーズに対応する強力なツールセットを提供します。
ベストプラクティス
[編集]kotlin.collections
を使用する際のベストプラクティスはいくつかあります。以下にいくつか挙げてみましょう:
- 不変性の推奨: 不変なコレクション(
List
、Set
、Map
など)を使用することを検討してください。不変なコレクションは変更不可能でスレッドセーフであり、プログラムの安全性を向上させるのに役立ちます。// 不変なリスト val immutableList = listOf(1, 2, 3) // 不変なセット val immutableSet = setOf("apple", "orange", "banana") // 不変なマップ val immutableMap = mapOf(1 to "one", 2 to "two", 3 to "three")
- Nullableなコレクションの適切な扱い: コレクション内の要素が
null
になりうる場合、Nullableなコレクション(List?
、Set?
、Map?
など)を使用して適切にハンドリングしましょう。// Nullableなリスト val nullableList: List<Int>? = // ... // Nullableなセット val nullableSet: Set<String>? = // ... // Nullableなマップ val nullableMap: Map<Int, String>? = // ...
- 関数型プログラミングの活用:
filter
、map
、reduce
、fold
などの関数型プログラミングの機能を活用して、コードを簡潔で読みやすくしましょう。val numbers = listOf(1, 2, 3, 4, 5) val evenSquared = numbers .filter { it % 2 == 0 } .map { it * it } .sum()
- 適切なコレクションの選択: 使用ケースによって適切なコレクションを選択しましょう。例えば、要素の順序が重要な場合は
List
、一意性が必要な場合はSet
、キーと値のペアが必要な場合はMap
を使用します。// 要素の順序が重要 val orderedList = listOf(1, 2, 3, 4, 5) // 一意性が必要 val uniqueSet = setOf("apple", "orange", "banana") // キーと値のペアが必要 val keyValueMap = mapOf(1 to "one", 2 to "two", 3 to "three")
これらのベストプラクティスは、コードの品質、パフォーマンス、保守性を向上させるのに役立ちます。使用ケースによって最適なアプローチを選択することが重要です。
比較
[編集]kotlin.comparisons.*
入出力
[編集]kotlin.io.*
範囲
[編集]kotlin.ranges.*
シーケンス
[編集]kotlin.sequences.*
テキスト
[編集]kotlin.text.*
コレクション類似クラス
[編集][TODO:Rangeはコレクションではないので再分類が必要]
println((1..5).javaClass.kotlin)
の結果が示す通り、範囲リテラル1..5
はclass kotlin.ranges.IntRange
です。
コレクションは、Ranges以外にも、Sequences・Ranges・Lists・Arrays・Sets・Mapsなどがあります。これは網羅していませんし、上記の forプロトコルに従ったクラスを作れば、ユーザー定義のコレクションも作成できます。
- 様々なコレクション
fun main(args: Array<String>) { val collections = arrayOf( 1..5, 1..8 step 2, 5 downTo 1, 8 downTo 1 step 2, 'A'..'Z', listOf(2,3,5), setOf(7,11,13)) println("$collections(${collections.javaClass.kotlin})") for (collection in collections) { print(collection) print(": ") for (x in collection) { print(x) print(" ") } print(": ") println(collection.javaClass.kotlin) } }
- 実行結果
class kotlin.Array 1..5: 1 2 3 4 5 : class kotlin.ranges.IntRange 1..7 step 2: 1 3 5 7 : class kotlin.ranges.IntProgression 5 downTo 1 step 1: 5 4 3 2 1 : class kotlin.ranges.IntProgression 8 downTo 2 step 2: 8 6 4 2 : class kotlin.ranges.IntProgression A..Z: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z : class kotlin.ranges.CharRange [2, 3, 5]: 2 3 5 : class java.util.Arrays$ArrayList [7, 11, 13]: 7 11 13 : class java.util.LinkedHashSet
- 二重のforループで、外周はコレクションのコレクションで、内周は個々のコレクションの要素をイテレーションしています。
スコープ関数
[編集]repeat関数
[編集]Kotlinの標準ライブラリーにあるrepeat関数は、定数回の繰返しが必要な時に便利です[51]。
- repeat関数
fun main() { repeat(5) { println("it = $it") } run { repeat(3) { i -> repeat(4) { j -> println("(i, j) = ($i, $j)") if (i == 1 && j == 2) { return@run } } } } }
- 実行結果
it = 0 it = 1 it = 2 it = 3 it = 4 (i, j) = (0, 0) (i, j) = (0, 1) (i, j) = (0, 2) (i, j) = (0, 3) (i, j) = (1, 0) (i, j) = (1, 1) (i, j) = (1, 2)
- itは、暗黙のループ変数です。
- 多重ループでは、ループ変数の名前が固定では都合が悪いので、ブロックの先頭で
識別子名 ->
とすることで明示的に名前をつけることができます。 - 多重ループを run 関数で括ることで多重ループからの大域脱出を実現しています。
ブロックを受取る関数
[編集]repeat関数もそうですが、Kotlinにはブロックを受取る関数(やメソッド)があります。
- ブロックを受取る関数
fun main(args: Array<String>) { val ary = Array(5) { 2 * it + 1 } ary.forEach{ println(it) } println(ary.map{ it.toString() }.joinToString(" ")) println(ary.reduce{ sum, el -> sum + el }) }
- 実行結果
1 3 5 7 9 1 3 5 7 9 25
- ブロックで配列の初期化を行う場合、itは順位になります。
- コレクションのforEachメソッドもブロックを取ります。
- コレクションのreduceメソッドもブロックを取りますが、累算値と要素の2つを取るので、名付けが必要です。
このように、ブロックを取るメソッドを使うとコレクションに関する操作を簡素に書けます。
Coroutine
[編集]Kotlinは、言語レベルでコルーチンをサポートし、機能の大部分をライブラリに委ねることで、この問題を柔軟に解決しています。
- Shell
$ cat coroutine.kt import kotlinx.coroutines.* fun main() = runBlocking { launch { delay(500L) for (ch in "World!\n") { print(ch) delay(100L) } } print("Hello ") } $ locate /kotlinx-coroutines /usr/local/share/kotlin/lib/kotlinx-coroutines-core-jvm.jar $ kotlinc -cp /usr/local/share/kotlin/lib/kotlinx-coroutines-core-jvm.jar coroutine.kt -include-runtime -d coroutine.jar $ java -cp ./coroutine.jar:/usr/local/share/kotlin/lib/kotlinx-coroutines-core-jvm.jar CoroutineKt Hello World!
Kotlin Script
[編集]Kotlin Script では、.jar を生成せず、そのままコードが実行されます。 また、main 関数をエントリーポイントはぜず、スクリプトの書かれた順に評価します。
- Shell
% cat hello.kts println("Hello, World!") % kotlinc -script hello.kts Hello, World! % _
ビルドツールの Gradle では、従来は Groovy がビルド構成ファイルに使われていましたが、Kotlin Script への移行が進んでいます。
- ワンライナー
% kotlin -e 'repeat(3){println("Hello!($it)")}' Hello!(0) Hello!(1) Hello!(2) % _
改廃された技術
[編集]Kotlinの改廃された技術や利用が推奨されない技術は、言語やエコシステムの進化、新しい要求、セキュリティ上の懸念などにより置き換えられます。以下に、代表的な技術を示します。
Kotlin Android Extensions
[編集]- サポート開始年: 2015年
- サポート終了年: 2021年(Kotlin 1.6.20で非推奨化)
- 廃止または衰退の理由
- View BindingやJetpack Composeなど、より安全で効率的なUI開発ツールが普及したため、利用が非推奨化されました。
- 代替技術
- View BindingやJetpack Composeを使用することが推奨されます。
Synthetic Properties for Views
[編集]- サポート開始年: 2015年
- サポート終了年: 2021年(非推奨化)
- 廃止または衰退の理由
- Null安全性やビルドプロセスの問題から、公式に非推奨となりました。
- 代替技術
- View Bindingを使用することで、同様の機能を安全に実現できます。
Kotlin Scripting旧形式 (kotlin.script.experimental)
[編集]- 対象: kotlin.script.experimental APIの旧バージョン
- 利用推奨されない理由
- 新しいAPI(kotlin.script.experimental.jvmなど)がより柔軟で強力であるため、旧形式は非推奨となりました。
- 代替技術
- 最新のKotlin Scripting APIを使用してください。
Kotlin Coroutinesの旧形式
[編集]- 対象: kotlinx.coroutinesの旧バージョン(0.x系)
- 利用推奨されない理由
- 古いバージョンではAPIが安定しておらず、最新の1.x系バージョンでは大幅な改善と安定化が図られています。
- 推奨バージョン
- 最新の1.x系またはそれ以降を使用してください。
Experimental API
[編集]- 対象:
@Experimental
や@UseExperimental
アノテーションでラベル付けされたAPI
- 利用推奨されない理由
- 将来のリリースで仕様変更や削除が行われる可能性があるため、慎重に利用する必要があります。
- 代替技術
- 安定版APIがリリースされた場合、それに切り替えることが推奨されます。
Kotlin/NativeのC Interop旧形式
[編集]- 対象: Kotlin/NativeにおけるC Interopの旧形式
- 利用推奨されない理由
- 最新のKotlin/NativeバージョンでInteropが改善され、旧形式は非推奨となりました。
- 代替技術
- 最新のC Interop形式や公式ドキュメントに基づいた方法を使用してください。
Kotlin Multiplatform Pluginの旧形式
[編集]- 対象: Kotlin Multiplatform Pluginの旧バージョン
- 利用推奨されない理由
- 最新バージョンではモジュール分割や依存関係管理の改善が行われ、旧バージョンの利用は推奨されなくなりました。
- 代替技術
- 最新のKotlin Multiplatform Pluginを利用してください。
Java 6および7向けのサポート
[編集]- サポート開始年: 2011年(初期Kotlin)
- サポート終了年: 2020年(Kotlin 1.6以降で非推奨化)
- 廃止または衰退の理由
- 古いJavaバージョンの市場シェアが低下したため、KotlinチームはJava 8以降に焦点を当てています。
- 代替技術
- Java 8以降を対象にKotlinを使用することが推奨されます。
脚註
[編集]- ^ Package definition and imports
- ^ Default imports
- ^ topLevelObject
- ^ Basic types
- ^ Characters
- ^ 6.0 6.1 Char
- ^ String
- ^ kotlin/core/builtins/native/kotlin/String.kt
- ^ plus - Kotlin Programming Language
- ^ Raw strings
- ^ Array
- ^ Array
- ^ Primitive type arrays
- ^ Nothing
- ^ Nullable types and non-null types
- ^ Identifiers
- ^ Destructuring declarations
- ^ Expressions
- ^ Operator overloading
- ^ Kotlin language specification Chapter 8 Expressions 8.6 When expressions
- ^ loopStatement
- ^ whileStatement
- ^ doWhileStatement
- ^ forStatement
- ^ JavaScriptの
function
に相当し、C言語やJavaには関数定義用のキーワードはなく文脈から判断されます。 - ^ Single-expression functions
- ^ downTo
- ^ JavaScriptでは、スプレッド演算子と呼ばずスプレッド構文と呼ぶようになりました。
- ^ Higher-order functions
- ^ lambdas
- ^ Anonymous functions
- ^ Closures
- ^ Inline functions
- ^ Extensions
- ^ Extension functions
- ^ Member functions
- ^ This expressions
- ^ Visibility modifiers
- ^ Enum classes
- ^ Interfaces
- ^ Generics: in, out, where
- ^ Keywords and operators
- ^ Hard keywords
- ^ Soft keywords
- ^ Annotation use-site targets
- ^ Modifier keywords
- ^ Special identifiers
- ^ Operators and special symbols
- ^ Kotlin Standard Library
- ^ Package kotlin.collections
- ^ repeat - Kotlin Programming Language