メインページ > 工学 > 情報技術 > プログラミング > Kotlin


Wikipedia
Wikipedia
ウィキペディア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と書くべきところを inti32 と間違えることもなく、funfunctionfndef と書間違えることもありません。
ときどき「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 をコンパイル/実行するには hellofilename に読替えて同じ手順を行えばいいのですが、このような単純作業はコンピューターに任せましょう。

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つのメソッドがインスタンス化の機能が割振られます。
デストラクター
ありません。
継承
単一継承をサポートします。
抽象クラス
JavaGo の interface や Swift の protocol はありませんが、abstract class があります。
Mix-in
サポートします。
名前空間
パッケージが名前空間を持ちます。
defer
ありません。
分岐構造は式
if, whenは式です。
文末の;(セミコロン)
必要ありません。

コード・ギャラリー 編集

エラトステネスの篩 編集

エラトステネスの篩を、若干 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 は、コンパイラーに末尾再帰が行なわれていることを教え、再帰をループにするヒントを与えています。
関数 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
変数宣言
イミュータブル
val 識別子 : 型 = 初期化式 ;
型推論版
val 識別子 = 初期化式 ;
ミュータブル
var 識別子 : 型 = 初期化式 ;
リテラル
整数リテラル
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 -> 
}
式を省略すると true が仮定されます
when {
    式0 -> 式0
    式1, 式2... , n -> 式1
  elase -> 
}
値を返す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)
}
繰返し処理
while
do-while
for
コレクション
クラス
enum class
abstract class
関数
関数呼出し
関数定義

KotlinとJavaの比較 編集

KotlinとJavaは、両方ともJava Virtual Machine(JVM)上で動作するプログラミング言語です。以下は、KotlinとJavaの比較です。

  1. Null Safety: KotlinはNull Safetyの概念を導入しており、nullの参照が発生する可能性がある場合には、コンパイル時に警告を出します。JavaはNull Safetyの概念を持っていません。
  2. コードの簡潔性: KotlinはJavaよりもコードが短く、簡潔で読みやすくなっています。これは、Kotlinがnullチェックや型推論などを自動的に処理するためです。
  3. 関数型プログラミング: Kotlinは関数型プログラミングをサポートしており、ラムダ式や高階関数の使用が簡単になっています。Javaも関数型プログラミングをサポートしていますが、Kotlinの方がよりシンプルになっています。
  4. 互換性: KotlinはJavaのライブラリやフレームワークを使用することができます。また、JavaからKotlinに変換することもできます。逆に、JavaはKotlinのコードを直接使用することはできません。
  5. パフォーマンス: KotlinはJavaに比べて少し遅いと言われていますが、この違いはほとんどありません。Kotlinは、Javaのコードに変換されるため、Javaと同じパフォーマンスを実現することができます。
  6. ツールのサポート: Kotlinは、Javaと同じツールを使用できます。これは、EclipseやIntelliJ IDEAなどの統合開発環境、GradleやMavenなどのビルドツール、JUnitやMockitoなどのテストフレームワークなどです。

総合的に見ると、KotlinはJavaよりも簡潔で読みやすく、より現代的な言語機能を備えています。ただし、Javaのライブラリやフレームワークを使用する場合は、Javaを使用することが必要な場合があります。

KotlinとScalaの比較 編集

KotlinとScalaは、どちらもJVM(Java Virtual Machine)上で動作する静的型付け言語です。以下に、KotlinとScalaをいくつかの側面で比較してみます。

  1. パフォーマンス: Kotlinは、Javaと同等のパフォーマンスを提供し、Scalaよりも少ないメモリ使用量を必要とします。Scalaは、Kotlinよりも多くのメモリを使用しますが、Javaよりも高速であることが知られています。
  2. 文法: Kotlinの文法はJavaに似ておりより簡素で、Javaからの移行が容易であるため、Java開発者にとっては理解しやすいものです。一方、Scalaは、独自の機能を持つより複雑な文法を持っています。それにより、Scala開発者は、より高度な文法理解と経験が必要となります。
  3. 開発用途: Kotlinは、Androidアプリケーションの開発に最適化されていますが、Scalaは、複雑なシステムの開発に使用されることが多いです。
  4. 可読性: Kotlinは、Javaと同じように、シンプルで読みやすいコードを書くことができます。Scalaの場合、より高度な文法が使用されるため、可読性が低下する可能性があります。
  5. 拡張性: Scalaは、高度な関数型プログラミングのサポートにより、拡張性が高い言語です。一方、Kotlinは、よりシンプルでクラスベースのプログラミングスタイルを提供しています。

総合的に見ると、KotlinはJavaからの移行が容易であり、Androidアプリケーションの開発に最適化されています。一方、Scalaは、複雑なシステムの開発に適しており、高度な関数型プログラミングのサポートを提供しています。どちらの言語も、JVM上で動作するため、Javaの機能と互換性があります。選択する言語は、開発の目的や開発者のスキルセットによって異なることがあります。

KotlinとSwiftの比較 編集

KotlinとSwiftは、どちらも現代のプログラミング言語であり、AndroidとiOSアプリケーションの開発に広く使用されています。以下では、KotlinとSwiftのいくつかの機能や性質を比較してみましょう。

  1. パフォーマンス:SwiftはiOSプラットフォーム上で非常に高速で効率的なコードを生成することができます。Kotlinは、Java Virtual Machine(JVM)上で実行されるため、Javaと同様に一般的に速度が遅いとされています。
  2. シンタックス:Kotlinは、JavaやC#などの言語に似た構文を持ち、Javaコードの相互運用性が高いことが特徴です。Swiftは、Objective-Cに似た構文を持っていますが、完全に独立しているため、Objective-Cコードとの相互運用性が限定的です。
  3. 可読性:Kotlinは、Javaと同様にオブジェクト指向プログラミング(OOP)の原則に従っており、非常に読みやすく保守性が高いコードを書くことができます。Swiftも、OOPの原則に従っており、可読性が高いコードを書くことができます。
  4. コミュニティサポート:Kotlinは、Googleがサポートしている言語であり、Android開発者の間で人気があります。Swiftは、Appleがサポートしている言語であり、iOS開発者の間で人気があります。どちらの言語も、非常に活発なコミュニティがあり、豊富なリソースやサポートがあります。
  5. ツールサポート:Kotlinは、IntelliJ IDEA、Android Studio、Eclipseなどの主要な統合開発環境(IDE)でサポートされています。Swiftは、Xcode IDEでサポートされています。
  6. 安全性: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 することなく使うことができます。

また、ターゲットプラットフォームに応じて、追加のパッケージがインポートされます。

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の他には、ScalaD言語がコメントを入れ子にできます。

基本型 編集

Kotlinでは、任意のインスタンスに対してプロパティーを参照したりメンバー関数を呼出すことができるという意味で、すべてのインスタンスがクラスに属しています。 いくつかの型は特別な内部表現を持つことができます。例えば、数字、文字、ブール値は実行時にプリミティブ値として表現できますが、ユーザーからは参照されるときに自動的にボックス化されるので普通のクラスのインスタンスのように見えます。 これらの型は基本的にJavaのプリミティブに一対一に対応しますが、Stringだけはjava.lang.Stringクラスに対応しています。 基本型は Package kotlin で定義されています。

Kotlinの基本型
論理型
Boolean  — true, false
数値型
符号付き整数型
Byte Short Int Long
符号無し整数型
UByte UShort UInt ULong
浮動小数点数型
FloatDouble
文字型
Char
文字列型
String
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 で定義されています。

特別な型
Kotlinのクラス階層のルート
Any
戻値型未指定な関数の型
Unit
存在しない値を表す型
Nothing

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!" になります。

型推論 編集

いままでの例で既に型推論( 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 + ba.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は、ifwhen の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 
零
条件式の条件 編集

ifwhile の条件式は

  • 論理型
  • 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には、whiledo-whilefor の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つの引数 12 を渡します。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
RubyInteger#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 04.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を持てないので関数リテラルの出番があります)。

クロージャー 編集

 
Wikipedia
ウィキペディアクロージャーの記事があります。

ラムダ式や無名関数(ローカル関数やオブジェクト式も同様)は、外部スコープで宣言された変数を含むクロージャー( 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 )は、キーワード enumclass に前置して定義します[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
    1. 型キャスト( type casts )に使用されます。
    2. インポートの別名の指定に使用されます。
  • as? 安全なタイプキャスト( safe type casts )に使用されます。
  • break ループの実行を終了させます。
  • class クラスを宣言します。
  • continue 最も近いループの次のステップに進みます。
  • do do/whileループ(条件式を後置するつループ)を開始します。
  • else 条件が偽のときに実行されるif式の分岐を定義します。
  • false Boolean型の「偽」の値を表します。
  • for forループを開始します。
  • fun 関数を宣言します。
  • if if式の先頭です。
  • in
    1. forループで反復されるオブジェクトを指定します。
    2. 値が範囲、コレクション、または「contains」メソッドを定義している他のエンティティに属しているかどうかを確認するための中置演算子として使用されます。
    3. 同じ目的のためにwhen式で使用されます。
    4. 型パラメータをcontravariantとしてマークします。
  • !in
    1. 値が範囲、コレクション、または 'contains' メソッドを定義する他のエンティティに属さないことを確認する演算子として使用されます。
    2. 同じ目的のためにwhen式で使用されます。
    !inで1つのトークンなので、! と in の間に空白を挟むことはできません。
  • interface インターフェース( interfaces )を宣言します。
  • is
    1. 値が特定の型を持つかどうかをチェックします。
    2. 同じ目的のwhen式で使用されます。
  • !is
    1. 値が特定の型を持っていないかどうかをチェックします。
    2. 同じ目的のwhen式で使用されます。
    !isで1つのトークンなので、! と is の間に空白を挟むことはできません。
  • null どのオブジェクトも指していないオブジェクト参照を表す定数です。
  • object クラスとそのインスタンスを同時に宣言します。
  • package 現在のファイルのパッケージを指定します。
  • return 最も近い包含関数または無名関数からの呼出し元に戻ります。
  • super
    1. メソッドやプロパティのスーパークラス実装を参照します。
    2. 二次コンストラクタ( secondary constructor )からスーパークラスのコンストラクタを呼び出します。
  • this
    1. 現在のレシーバを指します。
    2. 2 次コンストラクタから同じクラスの別のコンストラクタを呼び出します。
  • throw 例外を投げます。
  • true Boolean型の「真」の値を表します。
  • try 例外処理ブロックを開始します。
  • typealias 型の別名を宣言します。
  • typeof 将来の使用のために予約されています。
  • val 読取り専用のプロパティまたはローカル変数を宣言します。
  • var 変更可能なプロパティまたはローカル変数を宣言します。
  • when when式を開始します(与えられた分岐のうち1つを実行します)。
  • while while ループ(条件式を前置するループ)を開始します。

ソフト・キーワード 編集

以下のトークンは、それが適用される文脈( context )ではキーワードとして機能し、他の文脈では識別子として使用することができます[44]。 このようなキーワードをソフト・キーワード( Soft keywords )と呼びます。

アノテーション使用側ターゲット 編集

プロパティや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]

  • +, -, *, /, %  — 算術演算子
    1. * は、vararg パラメーターに配列を渡す場合にも使用されます。
  • =
    1. 代入演算子
    2. パラメーターのデフォルト値を指定するために使用されます。
  • +=, -=, *=, /=, %=  — 拡張された代入演算子。
  • ++, --  — インクリメントおよびデクリメント演算子
  • &&, ||, !  — 論理 'and', 'or', 'not' 演算子 (ビット演算には、対応する infix 関数を使用してください)。
  • ==, !=  — 等号演算子 (非プリミティブ型では equals() に変換される)。
  • ===, !==  — 参照系等号演算子( referential equality operators )
  • <, >, <=, >=  — 比較演算子 (非プリミティブ型に対する compareTo() の呼出しに変換されます)
  • [, ]  — インデックス付きアクセス演算子(getとsetの呼び出しに変換されます)
  • !! 式が非nullであることを保証します。
  • ?. 安全な呼び出しを行います(レシーバーが非NULLの場合、メソッドを呼び出したり、プロパティにアクセスしたりします)。
  • ?: 左辺の値がNULLの場合、右辺の値を取ります(エルビス演算子)。
  • :: メンバー参照またはクラス参照を作成します。
  • .. 範囲( a range )を生成します。
  • : 宣言の中で、名前と型を分離します。
  • ? 型をnull可能( as nullable )であるとマークします。
  • ->
    1. ラムダ式のパラメーターと本体を分離します。
    2. 関数型のパラメーターと戻値の型宣言を分離します。
    3. when式の条件分岐と本体を分離します。
  • @
    1. アノテーションを導入します。
    2. ループラベルを導入または参照します。
    3. ラムダ・ラベルを導入または参照します。
    4. 外部スコープから 'this' 式を参照します。
    5. 外部のスーパークラスを参照します。
  • ; 同じ行にある複数のステートメントを区切ります。
  • $ 文字列テンプレート内で変数または式を参照します。
  • _
    1. ラムダ式で使用しないパラメーターを置換えます。
    2. 構造化宣言の未使用のパラメーターを代入します。

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 ディレクティブでインポートすることなく使用できます。

[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..5class 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)
% _

脚註 編集

  1. ^ Package definition and imports
  2. ^ Default imports
  3. ^ topLevelObject
  4. ^ Basic types
  5. ^ Characters
  6. ^ 6.0 6.1 Char
  7. ^ String
  8. ^ kotlin/core/builtins/native/kotlin/String.kt
  9. ^ plus - Kotlin Programming Language
  10. ^ Raw strings
  11. ^ Array
  12. ^ Array
  13. ^ Primitive type arrays
  14. ^ Nothing
  15. ^ Nullable types and non-null types
  16. ^ Identifiers
  17. ^ Destructuring declarations
  18. ^ Expressions
  19. ^ Operator overloading
  20. ^ Kotlin language specification Chapter 8 Expressions 8.6 When expressions
  21. ^ loopStatement
  22. ^ whileStatement
  23. ^ doWhileStatement
  24. ^ forStatement
  25. ^ JavaScriptのfunctionに相当し、C言語やJavaには関数定義用のキーワードはなく文脈から判断されます。
  26. ^ Single-expression functions
  27. ^ downTo
  28. ^ JavaScriptでは、スプレッド演算子と呼ばずスプレッド構文と呼ぶようになりました。
  29. ^ Higher-order functions
  30. ^ lambdas
  31. ^ Anonymous functions
  32. ^ Closures
  33. ^ Inline functions
  34. ^ Extensions
  35. ^ Extension functions
  36. ^ Member functions
  37. ^ This expressions
  38. ^ Visibility modifiers
  39. ^ Enum classes
  40. ^ Interfaces
  41. ^ Generics: in, out, where
  42. ^ Keywords and operators
  43. ^ Hard keywords
  44. ^ Soft keywords
  45. ^ Annotation use-site targets
  46. ^ Modifier keywords
  47. ^ Special identifiers
  48. ^ Operators and special symbols
  49. ^ Kotlin Standard Library
  50. ^ Package kotlin.collections
  51. ^ repeat - Kotlin Programming Language

外部リンク 編集