Kotlin
メインページ > 工学 > 情報技術 > プログラミング > 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™商標を守っています。
はじめに[編集]
予備知識[編集]
読者が予備知識として、他のプログラミング言語の知識を持っていることのメリットについて考えてみます。
- Java
- Javaについての知識があると、特にKotlin/JVMでは、Javaのプリミティブ集合とクラスライブラリーの知識が利用できるので有利です。
- CとC++の関係と違い、JavaとKotlinの文法や型システムは異なるので、新たに学ぶ必要があります。
- Kotlinは、(Scalaと同じく)「ベターJava」として開発されたので、「Kotlinは学ぶために、まずJavaを学ぶ」必要性はありません(止めはしません)。
- JavaScript
- Javaについての知識があると、特にKotlin/JSでは、生成されるJavaScriptのコードが読めるのは、学ぶ上で有利になります。
- JavaScriptとKotlinの文法や型システムは異なるので、新たに学ぶ必要があります。
- Scala
- ScalaとKotlinの文法や型システムは(JavaやJavaScriptと比べれば)似通っているので、ある程度はScalaの知識は役に立ちます。
- ただ、Scala3で構文の大幅な見直しがある差が広がる傾向にあります。
- 「Kotlinは学ぶために、まずScalaを学ぶ」必要性はありません(止めはしません)。
- 特にプログラミング言語の知識がない
- あなたはとてもラッキーです。
- 行の終わりに余計な
;
を書いたり、Int
と書くべきところをint
やi32
と間違えることもなく、fun
をfunction
やfn
やdef
と書間違えることもありません。 - ときどき「Javaの〇〇とは違い〜」のような文章が気になるかもしれませんが、そのまま読み流してください。害はありません。
Kotlinはほかにも、Groovy経由でRubyの影響を受けているので、Rubyの知識があるとメソッドの最後の引数にラムダ式を使える構文に既視感と使い道についての目星が付くというメリットがあります。
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! % _
インストール方法[編集]
Kotlinは、ターゲットごとに
- Kotlin/JVM
- Java仮想マシン(JVM)で実行可能なコードを生成。
- Kotlin/JS
- JavaScriptのコードを生成。
- Kotlin/Native
- バックエンドにLLVMインフラストラクチャー を利用してネイティブコードコードを生成。
の3つの実装があり、ツールチェインとしては統合されていますが、使用するコマンドやオプションが異なります。
Kotlin/JVM 環境のインストール[編集]
Windows[編集]
AdoptOpenJDKのインストール[編集]
オラクルのJDKのライセンスがフリーではなくなったので、Eclipse Temurin のビルド済み OpenJDK をインストールします。
Eclipse Temurin™ Latest Releasesから
- Operating Syatem — Windows
- Architecture — 適合したアーキテクチャー
- Package Type — JDK
- Version — 希望するバージョン
を選んでダウンロード・インストールします。
kotlinの入手[編集]
GitHubにKotlinの公式リポジトリがあります。
https://github.com/JetBrains/kotlin/
このリポジトリから、リリース情報を開き、Assets をページ内検索しその章にある kotlin-compiler-1.9.0 をダウンロードします。
1.9.0は2023年7月11日時点での最新版ですが、リリースごとに更新されていくため、適宜読み替えてください。
kotlin のインストール[編集]
GitHubからダウンロードしたkotlinコンパイラのZIPは、ここでは説明のためC:\kotlinc にを移動したとします(末尾に「c」がついています)。
この後は、 C:\kotlinc\bin にパスを通します。
- 同じマシンを利用するユーザーで、Kotlin のコンパイル環境を共有する場合
- システム環境変数のPATHの先頭に C:\kotlinc\bin を追加します。
- 手順
- ”システムのプロパティ” を検索し、[システムのプロパティ]を開き、[詳細設定]タブの右下にある[環境変数]を選択し、[環境変数]画面を開き、「システム環境変数」を書換えます。
- 自分のアカウントだけで、Kotlin のコンパイル環境を利用する場合
- ユーザー環境変数のPATHの先頭に C:\kotlinc\bin を追加します。
- 手順
- ”システムのプロパティ” を検索し、[システムのプロパティ]を開き、[詳細設定]タブの右下にある[環境変数]を選択し、[環境変数]画面を開き、「ユーザー環境変数」を書換えます。
環境変数の設定ができたら、バージョンを確認します。
インストールが終わったら、インストールされたKotlinのバージョンを確認します。
- バージョン確認
% kotlinc -version Kotlin version 1.9.0-release-358 (JRE 1.8.0_372-b07)
もし
C:> kotlinc -version kotlinc: Command not found.
の様に、失敗するようでしたらインストール失敗も考えられますが、C:\kotlinc\bin にPATHが通っているか確認してください。
BSD系Unixの場合[編集]
NetBSDやFreeBSDなどのBSD系Unixの場合、Package sourceやPorts Collectionに、lang/kotlin としてエントリーがあるので
- ソースからビルド
# make -C /usr/ports/lang/kotlin all install clean ===> License APACHE20 accepted by the user ===> kotlin-1.9.0 depends on file: /usr/local/sbin/pkg - found ===> Fetching all distfiles required by kotlin-1.9.0 for building ===> Extracting for kotlin-1.9.0 => SHA256 Checksum OK for kotlin-compiler-1.9.0.zip. /bin/rm -f /usr/ports/lang/kotlin/work/kotlinc/bin/*.bat ===> Patching for kotlin-1.9.0 ===> Configuring for kotlin-1.9.0 ===> Staging for kotlin-1.9.0 ===> kotlin-1.9.0 depends on executable: bash - found ===> kotlin-1.9.0 depends on file: /usr/local/openjdk8/bin/java - found ===> Generating temporary packing list /bin/mkdir -p /usr/ports/lang/kotlin/work/stage/usr/local/share/kotlin/lib /bin/mkdir -p /usr/ports/lang/kotlin/work/stage/usr/local/share/kotlin/bin cd /usr/ports/lang/kotlin/work/kotlinc/bin && /bin/sh -c '(/usr/bin/find -Ed $1 $3 | /usr/bin/cpio -dumpl $2 >/dev/null 2>&1) && /usr/bin/find -Ed $1 $3 \( -type d -exec /bin/sh -c '\''cd '\''$2'\'' && chmod 755 "$@"'\'' . {} + -o -type f -exec /bin/sh -c '\''cd '\''$2'\'' && chmod 555 "$@"'\'' . {} + \)' COPYTREE_BIN . /usr/ports/lang/kotlin/work/stage/usr/local/share/kotlin/bin cd /usr/ports/lang/kotlin/work/kotlinc/lib && /bin/sh -c '(/usr/bin/find -Ed $1 $3 | /usr/bin/cpio -dumpl $2 >/dev/null 2>&1) && /usr/bin/find -Ed $1 $3 \( -type d -exec /bin/sh -c '\''cd '\''$2'\'' && chmod 755 "$@"'\'' . {} + -o -type f -exec /bin/sh -c '\''cd '\''$2'\'' && chmod 0644 "$@"'\'' . {} + \)' COPYTREE_SHARE . /usr/ports/lang/kotlin/work/stage/usr/local/share/kotlin/lib /bin/ln -sf /usr/local/share/kotlin/bin/kapt /usr/ports/lang/kotlin/work/stage/usr/local/bin/kapt /bin/ln -sf /usr/local/share/kotlin/bin/kotlin /usr/ports/lang/kotlin/work/stage/usr/local/bin/kotlin /bin/ln -sf /usr/local/share/kotlin/bin/kotlin-dce-js /usr/ports/lang/kotlin/work/stage/usr/local/bin/kotlin-dce-js /bin/ln -sf /usr/local/share/kotlin/bin/kotlinc /usr/ports/lang/kotlin/work/stage/usr/local/bin/kotlinc /bin/ln -sf /usr/local/share/kotlin/bin/kotlinc-js /usr/ports/lang/kotlin/work/stage/usr/local/bin/kotlinc-js /bin/ln -sf /usr/local/share/kotlin/bin/kotlinc-jvm /usr/ports/lang/kotlin/work/stage/usr/local/bin/kotlinc-jvm install -C -m 0644 /usr/ports/lang/kotlin/work/kotlinc/build.txt /usr/ports/lang/kotlin/work/stage/usr/local/share/kotlin ====> Compressing man pages (compress-man) ===> Installing for kotlin-1.9.0 ===> Checking if kotlin is already installed ===> Registering installation for kotlin-1.9.0 Installing kotlin-1.9.0... ===> Cleaning for kotlin-1.9.0
あるいは
- パッケージからインストール
# pkg install lang/kotlin Updating FreeBSD repository catalogue... FreeBSD repository is up to date. All repositories are up to date. The following 2 package(s) will be affected (of 0 checked): New packages to be INSTALLED: kotlin: 1.9.0 Installed packages to be DOWNGRADED: highway: 1.0.0 -> 0.17.0 Number of packages to be installed: 1 Number of packages to be downgraded: 1 The process will require 77 MiB more space. 354 KiB to be downloaded. Proceed with this action? [y/N]: y [1/1] Fetching highway-0.17.0.pkg: 100% 354 KiB 362.7kB/s 00:01 Checking integrity... done (0 conflicting) [1/2] Installing kotlin-1.9.0... [1/2] Extracting kotlin-1.9.0: 100% [2/2] Downgrading highway from 1.0.0 to 0.17.0... [2/2] Extracting highway-0.17.0: 100%
- の2通りのインストール方法があります。
ソースからビルドと言っも、lang/kotlin の場合は2023年7月現在、GitHubからリリースバージョンのコンパイラーのZIPを fetch して展開するだけなので、ビルドオプションを変えてホスト環境に最適化などはしなしので、パッケージ版との差異はありません。
なお、どちらの方法も、jdk などのパッケージに不足があれば、依存関係により、ビルドあるいは fetch & install されます。
インストールが終わったら、インストールされたKotlinのバージョンを確認します。
- バージョン確認
% kotlinc -version info: kotlinc-jvm 1.9.0 (JRE 1.8.0_332-b09)
もし
% kotlinc -version kotlinc: Command not found.
の様に、失敗するようでしたらインストール失敗も考えられますが、/usr/local/bin にPATHが通っているか確認してください。
GNU/Linuxのディストリビューションの場合[編集]
kotlinをインストールのために、まず先にsdkmanをインストールします。
sdkmanはkotlinに限らず、パッケージの複数バージョンの並行管理などを行うことができます。
sdkmanのインストール[編集]
$ curl -s "https://get.sdkman.io" | bash
でsdkmanのインストールを行ないます。
アスキーアートが表示され、
Now attempting installation... Looking for a previous installation of SDKMAN... Looking for unzip... Looking for zip...
(※ 後略) と処理が進み
最後に
All done!
あるいは
Enjoy!!!
とか書いてあれば、sdkmanのインストール自体は完了です。 この時点では、パス設定などはまだされていません。
そのあと、sdkman にパスを通すため
source "$HOME/.sdkman/bin/sdkman-init.sh"
を実行します。
このあと、インストールが成功したかどうかの確認のため
sdk version
もし sdkman のインストールに成功してれば、
===== BROADCAST ================================================================== * 2020-06-17: Asciidoctorj 2.3.1 released on SDKMAN! #asciidoctorj * 2020-06-16: Micronaut 2.0.0.RC1 released on SDKMAN! #micronautfw * 2020-06-14: Jbang 0.31.0 released on SDKMAN! See https://github.com/jbangdev/jbang/releases/tag/v0.31.0 #jbang ================================================================================ SDKMAN 5.8.3+506
のようなう表示が行われます。
ここまでで、sdkmanがインストールされました。
sdkmanのインストール後[編集]
sdkmanのインストールに成功したら、kotlin のインストールを行います。
sdk install kotlin
でkotlinのインストールが開始されます。
成功すれば、下記のように表示されます。
Downloading: kotlin 1.9.0 In progress... ######################################################################### 100.0%######################################################################### 100.0% Installing: kotlin 1.9.0 Done installing! Setting kotlin 1.9.0 as default.
これで kotlin のインストールは完了です。
インストールが終わったら、インストールされたKotlinのバージョンを確認します。
- バージョン確認
$ kotlinc -version info: kotlinc-jvm 1.9.0 (JRE 1.8.0_332-b09)
もし
$ kotlinc -version kotlinc: Command not found.
の様に、失敗するようでしたらインストール失敗も考えられますが、kotlinc にPATHが通っているか確認してください。
実行方法[編集]
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; print("$i ") for (j in 2 * i until sieve.size step i) sieve[j] = false } } 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(m: Int, n: Int) : Int = if (n == 0) m else gcd2(n, m % n) fun gcd(vararg ints: Int) = ints.reduce{ x, y -> gcd2(x, y) } fun lcm2(m: Int, n: Int) = m * n / gcd2(m, n) fun lcm(vararg ints: Int) = ints.reduce{ x, y -> lcm2(x, y) } 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: KotlinはNull Safetyの概念を導入しており、nullの参照が発生する可能性がある場合には、コンパイル時に警告を出します。JavaはNull Safetyの概念を持っていません。
- コードの簡潔性: KotlinはJavaよりもコードが短く、簡潔で読みやすくなっています。これは、Kotlinがnullチェックや型推論などを自動的に処理するためです。
- 関数型プログラミング: Kotlinは関数型プログラミングをサポートしており、ラムダ式や高階関数の使用が簡単になっています。Javaも関数型プログラミングをサポートしていますが、Kotlinの方がよりシンプルになっています。
- 互換性: KotlinはJavaのライブラリやフレームワークを使用することができます。また、JavaからKotlinに変換することもできます。逆に、JavaはKotlinのコードを直接使用することはできません。
- パフォーマンス: KotlinはJavaに比べて少し遅いと言われていますが、この違いはほとんどありません。Kotlinは、Javaのコードに変換されるため、Javaと同じパフォーマンスを実現することができます。
- ツールのサポート: Kotlinは、Javaと同じツールを使用できます。これは、EclipseやIntelliJ IDEAなどの統合開発環境、GradleやMavenなどのビルドツール、JUnitやMockitoなどのテストフレームワークなどです。
総合的に見ると、KotlinはJavaよりも簡潔で読みやすく、より現代的な言語機能を備えています。ただし、Javaのライブラリやフレームワークを使用する場合は、Javaを使用することが必要な場合があります。
KotlinとScalaの比較[編集]
KotlinとScalaは、どちらもJVM(Java Virtual Machine)上で動作する静的型付け言語です。以下に、KotlinとScalaをいくつかの側面で比較してみます。
- パフォーマンス: Kotlinは、Javaと同等のパフォーマンスを提供し、Scalaよりも少ないメモリ使用量を必要とします。Scalaは、Kotlinよりも多くのメモリを使用しますが、Javaよりも高速であることが知られています。
- 文法: Kotlinの文法はJavaに似ておりより簡素で、Javaからの移行が容易であるため、Java開発者にとっては理解しやすいものです。一方、Scalaは、独自の機能を持つより複雑な文法を持っています。それにより、Scala開発者は、より高度な文法理解と経験が必要となります。
- 開発用途: Kotlinは、Androidアプリケーションの開発に最適化されていますが、Scalaは、複雑なシステムの開発に使用されることが多いです。
- 可読性: Kotlinは、Javaと同じように、シンプルで読みやすいコードを書くことができます。Scalaの場合、より高度な文法が使用されるため、可読性が低下する可能性があります。
- 拡張性: Scalaは、高度な関数型プログラミングのサポートにより、拡張性が高い言語です。一方、Kotlinは、よりシンプルでクラスベースのプログラミングスタイルを提供しています。
総合的に見ると、KotlinはJavaからの移行が容易であり、Androidアプリケーションの開発に最適化されています。一方、Scalaは、複雑なシステムの開発に適しており、高度な関数型プログラミングのサポートを提供しています。どちらの言語も、JVM上で動作するため、Javaの機能と互換性があります。選択する言語は、開発の目的や開発者のスキルセットによって異なることがあります。
KotlinとSwiftの比較[編集]
KotlinとSwiftは、どちらも現代のプログラミング言語であり、AndroidとiOSアプリケーションの開発に広く使用されています。以下では、KotlinとSwiftのいくつかの機能や性質を比較してみましょう。
- パフォーマンス:SwiftはiOSプラットフォーム上で非常に高速で効率的なコードを生成することができます。Kotlinは、Java Virtual Machine(JVM)上で実行されるため、Javaと同様に一般的に速度が遅いとされています。
- シンタックス:Kotlinは、JavaやC#などの言語に似た構文を持ち、Javaコードの相互運用性が高いことが特徴です。Swiftは、Objective-Cに似た構文を持っていますが、完全に独立しているため、Objective-Cコードとの相互運用性が限定的です。
- 可読性:Kotlinは、Javaと同様にオブジェクト指向プログラミング(OOP)の原則に従っており、非常に読みやすく保守性が高いコードを書くことができます。Swiftも、OOPの原則に従っており、可読性が高いコードを書くことができます。
- コミュニティサポート:Kotlinは、Googleがサポートしている言語であり、Android開発者の間で人気があります。Swiftは、Appleがサポートしている言語であり、iOS開発者の間で人気があります。どちらの言語も、非常に活発なコミュニティがあり、豊富なリソースやサポートがあります。
- ツールサポート:Kotlinは、IntelliJ IDEA、Android Studio、Eclipseなどの主要な統合開発環境(IDE)でサポートされています。Swiftは、Xcode IDEでサポートされています。
- 安全性:Kotlinは、Null安全性の概念を持ち、プログラマーが安全にコードを書くことができるようになっています。Swiftも、Optional型をサポートすることにより、Nullポインターの例外を回避することができます。
以上のように、KotlinとSwiftにはそれぞれの特徴があります。どちらが優れているかは、開発者の必要性や好みによって異なります。しかし、どちらの言語も、モダンなアプリケーション開発に適していることは間違いありません。
エントリーポイント[編集]
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
ディレクトリとパッケージの一致は必須ではなく、ソースファイルはファイルシステム上に任意に配置することができます。
ディフォルトインポート[編集]
以下のパッケージは、明示的にインポートすることなく既にインポートされています[2]。
- たとえば、
println()
はパッケージはkotlin.io.println
で定義されていますが、import kotlin.io.println
することなく使うことができます。
- kotlin.*
- kotlin.annotation.*
- kotlin.collections.*
- kotlin.comparisons.*
- kotlin.io.*
- kotlin.ranges.*
- kotlin.sequences.*
- kotlin.text.*
また、ターゲットプラットフォームに応じて、追加のパッケージがインポートされます。
- JVM
- JS
インポート[編集]
デフォルトインポートとは別に、各ファイルは独自の 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の数値型には、整数型と浮動小数点数型があります。 Kotlinの数値型は Number クラスから派生しています(Numberクラス自体は、抽象クラスなのでインスタンス化できません)。 また、文字型は数値型と可換ではありません。
整数型[編集]
Kotlinの整数型には、符号付き整数型と符号なし整数型があり、符号なし整数型の名前は符号付き整数型の名前の先頭に U を補ったものになります(Int ⇒ UInt, Long ⇒ ULong)。
Byte
— 符号付き1バイト整数Short
— 符号付き2バイト整数Int
— 符号付き4バイト整数Long
— 符号付き8バイト整数UByte
— 符号なし1バイト整数UShort
— 符号なし2バイト整数UInt
— 符号なし4バイト整数ULong
— 符号なし8バイト整数
浮動小数点数型[編集]
Kotlinの浮動小数点数型には
Float
(単精度浮動小数点数;ISO/IEC/IEEE 60559:2011 のbinary32)Double
(倍精度浮動小数点数;ISO/IEC/IEEE 60559:2011 のbinary64)
の2つがあります。
文字型[編集]
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安全[編集]
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コードによって引き起こされる問題。
Kotlinの型システムでは、nullを保持できる参照(Null可能参照; nullable references )とそうでない参照(非Null参照; non-null references )を区別しています。例えば、String型の通常の変数はnullを保持できません。
var a: String = "abc" // 通常の初期化ではデフォルトで非nullを意味します a = null // コンパイルエラー!
nullを許容するには、String?
と書いて変数をnull可能な文字列として宣言します。
var a: String? = "abc" // nullに設定可能 b = null // OK print(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を発生させたい場合は、明示的に要求する必要があり、突然発生することはありません。
識別子[編集]
変数の名前のような名前を識別子( 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
- 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") }
[TODO:境界値を省略した例、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' 修飾子はこの関数には適用できません。メンバーか拡張関数ではないからです。
関数スコープ[編集]
[TODO]
可変長引数[編集]
Kotlin で可変長引数( variable-length arguments )を持つ関数を定義するには、キーワード vararg
とスプレッド演算子( spread operator[28] )を使います。
- 可変長引数のコード例
fun main() { myVaPrint("abc", "def", "xyz") } fun myVaPrint(vararg values: String) { for (s in values) println(s) }
- 実行結果
abc def xyz
[TODO:スプレッド演算子]
高階関数[編集]
引数あるいは戻値あるいは両方が関数の関数を高階関数()と呼びます[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[編集]
[TODO: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
は、メンバー関数のオーバーライド許可でも使われます。
Kotlinのクラスはディフォルトでfinal |
Javaと異なり、Kotlinのクラスはディフォルトでfinal(継承禁止)です。
これは、主にセキュリティー上の理由からで、クラスを継承することにより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 )だけを持つクラスです。
モディファイア・キーワード abstract
は、抽象メソッドの定義でも使われます。
[TODO]
ファイナルクラス[編集]
ファイナルクラス( final class )は、継承を禁止したクラスです。
KotlinのクラスはJavaと異なり、ディフォルトで継承禁止なので、継承禁止を強調する意味しかありません。
モディファイア・キーワード final
は、オーバーライド禁止メンバーの宣言でも使われます。
[TODO]
オープンクラス[編集]
オープンクラス( open class )は、継承を許可したクラスです。
KotlinのクラスはJavaと異なり、ディフォルトで継承禁止なので、継承を行う可能性がある場合は明示的に許可する必要があります。
モディファイア・キーワード open
は、メソッドのオーバーライド許可でも使われます。
[TODO]
列挙型クラス[編集]
列挙型クラス( 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 )は、制限されたクラス階層を表現するために使用されます。
[TODO:コード例]
アノテーションクラス[編集]
アノテーションクラス( annotation class )は、コードにメタデータを付加するための手段を提供します。
[TODO:コード例]
データークラス[編集]
データークラス( Data class )は、データーを保持するためのクラスで、copy() などのメソッドがクラスを定義しただけで生えてきます。
[TODO:コード例]
interface[編集]
Kotlinのインターフェース( interfaces )は、抽象的なメソッドの宣言と、メソッドの実装を含むことができます。抽象クラスと異なるのは、インターフェイスは状態を保持できないことです。インターフェイスはプロパティを持つことができますが、これらは抽象クラスであるか、アクセサーの実装を提供する必要があります[40]。
- インターファースは、インスタンス化できません。
- インターファースは、継承と同じ構文で新たに定義するクラスから参照されますが、これは準拠といいます。
- 継承は、1つのクラスからしかできませんが(単一継承)、インターフェースの準拠は複数可能で、インターフェース同士は
,
で区切ります。 - 同じ名前のメソッドを持つインターフェースを重ねて準拠することはできます。
- インターフェースは、メソッドのシグネチャを宣言するだけでなく、実装を定義することもできます。その場合、準拠先でオーバーライドしなければインターフェースの実装が評価されます。
- インターフェースが他のインターフェースを継承することができます。
[TODO:interface の存在意義は、実際に機能するコード(例えば、標準ライブラリの Iterator の実装)を読むのが一番ですが、引用してコメンタリーで解説するには巨大すぎるので、「使いどころとしてしっくり来る例」を考案中です。]
インナークラス[編集]
インナークラス( Inner class )は、入れ子になった内側になったクラスが外側のクラスのメンバーにアクセスすることを可能にします。
[TODO:コード例]
値クラス[編集]
値クラス( Value class )は、イミュータブルなスカラー値の型を定義します。プロポーザル段階では inline class と呼ばれていました。
[TODO:コード例]
オブジェクト[編集]
オブジェクト( object )は、モディファイア・キーワードではありませんが、特殊なクラスの一種なので併せて紹介します。 オブジェクトは一過的な匿名クラスを定義し、そのインスタンスの生成を行います。
[TODO:コード例]
ジェネリックス[編集]
ジェネリックス( 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 )を指定します。
アノテーション使用側ターゲット[編集]
プロパティや1次コンストラクタのパラメータにアノテーションを付ける場合、対応するKotlin要素から生成されるJava要素は複数あり、したがって生成されるJavaバイトコード内のアノテーションの位置も複数考えられます。アノテーションを正確に生成する方法を指定する必要があります[45]。
[TODO:アノテーション使用側ターゲットは annotation use-site target の訳語として適当か?]
モディファイア・キーワード[編集]
以下のトークンは、宣言の修飾語リスト( 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 などのコレクション型と関連するトップレベル関数と拡張関数か Package kotlin.collections で提供されています[50]。 kotlin.collections は、ディフォルトインポートの1つなので import ディレクティブでインポートすることなく使用できます。
- List --
- ArrayList --
- MutableList --
[TODO:インターフェースに言及すべき?]
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)。
比較[編集]
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) % _
脚註[編集]
- ^ 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