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


Wikipedia
ウィキペディアKotlinの記事があります。

Kotlinは、Java仮想マシン(JVM)上で動作する静的型付けされたオブジェクト指向のプログラミング言語で、JetBrains社によって開発されました。また、JavaScriptや、LLVMフレームワークを介して多くのプラットフォームで実行可能なコードにコンパイルすることができます。

開発者によると、Javaよりも簡潔で型安全性が高く、Scalaよりもシンプルな言語を作ることを目指したとしています。 Scalaと比較して簡略化された結果、コンパイルが高速化され、IDEでの言語サポートも充実しています。 この言語はJavaと完全な互換性があるため、Java開発者は徐々に導入することができます。 特に、この言語は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.7.21 をダウンロードします。

1.7.21 が2022年11月15日の最新版ですが、リリースの度に更新されていくので、適宜、読み替えてください。

kotlin のインストール編集

GitHubからダウンロードしたkotlinコンパイラのZIPは、ここでは説明のためC:\kotlinc にを移動したとします(末尾に「c」がついています)。

この後は、 C:\kotlinc\bin にパスを通します。

同じマシンを利用するユーザーで、Kotlin のコンパイル環境を共有する場合
システム環境変数のPATHの先頭に C:\kotlinc\bin を追加します。
手順
”システムのプロパティ” を検索し、[システムのプロパティ]を開き、[詳細設定]タブの右下にある[環境変数]を選択し、[環境変数]画面を開き、「システム環境変数」を書換えます。
自分のアカウントだけで、Kotlin のコンパイル環境を利用する場合
ユーザー環境変数のPATHの先頭に C:\kotlinc\bin を追加します。
手順
”システムのプロパティ” を検索し、[システムのプロパティ]を開き、[詳細設定]タブの右下にある[環境変数]を選択し、[環境変数]画面を開き、「ユーザー環境変数」を書換えます。

環境変数の設定ができたら、バージョンを確認します。

インストールが終わったら、インストールされたKotlinのバージョンを確認します。

バージョン確認
C:> kotlinc -version
 Kotlin version 1.7.21-release-201 (JRE 18.0.2.1+1-1)

もし

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.7.20 depends on file: /usr/local/sbin/pkg - found
===> Fetching all distfiles required by kotlin-1.7.20 for building
===>  Extracting for kotlin-1.7.20
=> SHA256 Checksum OK for kotlin-compiler-1.7.20.zip.
/bin/rm -f /usr/ports/lang/kotlin/work/kotlinc/bin/*.bat
===>  Patching for kotlin-1.7.20
===>  Configuring for kotlin-1.7.20
===>  Staging for kotlin-1.7.20
===>   kotlin-1.7.20 depends on executable: bash - found
===>   kotlin-1.7.20 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.7.20
===>  Checking if kotlin is already installed
===>   Registering installation for kotlin-1.7.20
Installing kotlin-1.7.20...
===>  Cleaning for kotlin-1.7.20

あるいは

パッケージからインストール
# 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.7.20

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.7.20...
[1/2] Extracting kotlin-1.7.20: 100%
[2/2] Downgrading highway from 1.0.0 to 0.17.0...
[2/2] Extracting highway-0.17.0: 100%
の2通りのインストール方法があります。

ソースからビルドと言っも、lang/kotlin の場合は2022年10月現在、GitHubからリリースバージョンのコンパイラーのZIPを fetch して展開するだけなので、ビルドオプションを変えてホスト環境に最適化などはしなしので、パッケージ版との差異はありません。

なお、どちらの方法も、jdk などのパッケージに不足があれば、依存関係により、ビルドあるいは fetch & install されます。

インストールが終わったら、インストールされたKotlinのバージョンを確認します。

バージョン確認
% kotlinc -version
info: kotlinc-jvm 1.7.20 (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.7.20

In progress...

######################################################################### 100.0%######################################################################### 100.0%

Installing: kotlin 1.7.20
Done installing!


Setting kotlin 1.7.20 as default.

これで kotlin のインストールは完了です。

インストールが終わったら、インストールされたKotlinのバージョンを確認します。

バージョン確認
$ kotlinc -version
info: kotlinc-jvm 1.7.20 (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やスクリプティングエンジンもあります。
Kotolin/JVM
1つまたは複数のソースコードをコンパイルしてJavaバイトコードを生成し、生成したJavaバイトコードを実行します。
Kotolin/JS
1つまたは複数のソースコードをコンパイルしてJavaScriptを生成し、生成したJavaScriptを実行します。
Kotolin/Native
1つまたは複数のソースコードをコンパイルしてLLVMインフラストラクチャーをバックエンドに実行形式を生成し、生成した実行形式を実行します。
静的型付け
値・変数・関数のパラメーター・関数の戻値などの型はコンパイル時に検証され、型安全性が担保されます。
例外
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!") 
}
変数宣言
イミュータブル
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では、関数 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 も同じで、新興言語は不用意な書換えによる古参言語で度々アクシデントのもととなった引数の破壊を永久になくしたいようです。

引数のディフォルト値編集

[TODO]

関数呼出し編集

関数指向の構文編集

関数名(実引数リスト)
[TODO:コード例]

メソッド指向の構文編集

インスタンス.関数名(実引数リスト)
メソッドあるいは拡張関数の呼出しはドット記法になります。
[TODO:コード例]

関数引数のある構文編集

関数呼出しの構文(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を持てないので関数リテラルの出番があります)。

クロージャー編集

ラムダ式や無名関数(ローカル関数やオブジェクト式も同様)は、外部スコープで宣言された変数を含むクロージャー( 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 )は、有限個の識別子の集合を表現するために使用されます。

sealed編集

シールドクラス( Sealed class )は、制限されたクラス階層を表現するために使用されます。

annotation編集

アノテーションクラス( annotation class )は、コードにメタデータを付加するための手段を提供します。

data編集

データークラス( Data class )は、データーを保持するためのクラスで、copy() などのメンバー関数がクラスを定義しただけで生えてきます。

inner編集

インナークラス( Inner class )は、入れ子になった内側になったクラスが外側のクラスのメンバーにアクセスすることを可能にします。

value編集

値クラス( Value class )は、イミュータブルなスカラー値の型を定義します。プロポーザル段階では inline class と呼ばれていました。

object編集

オブジェクト( object )は、モディファイア・キーワードではありませんが、特殊なクラスの一種なので併せて紹介します。 オブジェクトは一過的な匿名クラスを定義し、そのインスタンスの生成を行います。

可視性モディファイア編集

クラスオブジェクトインターフェースコンストラクター関数、およびプロパティとそのセッターは、可視性モディファイア( 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

外部リンク編集