Kotlin
メインページ > 工学 > 情報技術 > プログラミング > Kotlin
Kotlin(コトリン)は、クロスプラットフォームで、静的型付けな、型推論を用いることが出来る汎用プログラミング言語です。 Kotlinは、関数型プログラミング・クラス指向のオブジェクト指向型プログラミング・シェネリックプログラミングなど、複数のプログラミングパラダイムをサポートするマルチパラダイム言語です。 Kotlinは、Javaと完全に相互運用できるように設計されており、JVM版のKotlinの標準ライブラリはJavaクラスライブラリを利用しているが、型推論によってより簡潔な構文にすることが可能です。 Kotlinは、主にJVMをターゲットとしているが、JavaScript(Reactを用いたフロントエンドのWebアプリケーションなど)やLLVMによるネイティブコード(Androidアプリとビジネスロジックを共有するiOSネイティブアプリなど)にもコンパイルできます。 言語開発のコストは、JetBrainsが負担し、Kotlin FoundationがKotlin™商標を守っています。
目次[編集]
- Kotlin/インストール方法 ※ 長いので実行方法とは別の単元にします
- Kotlin/実行方法
- Kotlin/関数
- Kotlin/
未分類の下書き[編集]
※ 完成次第、サブページに移動してください。
- 予備知識
現状の版では、読者に予備知識として、ある程度のC言語やJavaScriptの知識を想定しています。もし読者がこれら予備知識の言語にそこまで詳しくなければ、先に『C言語』および『JavaScript』をお読みください。
なお、Javaの知識はあるにこしたことないですが、必ずしも必要ではありません。
Javaなどにある「クラス」を Kotlin でもサポートしており、Javaと同様にKotlin でも class 宣言でクラスを作成できます。しかし、読者がまったくクラスについて知らなくても、Kotlin による開発を比較的に簡単に始めることが可能です。
文法の概要[編集]
hello world[編集]
Kotlinの文法は、Javaと大きく異なります。
以下に示すのはKotlinでのw:ハローワールドのコードです。
- Hello.kt
fun main(args: Array<String>) { println("Hello, World!") }
- ShortHello.kt
fun main() { println("Hello, World!") }
と書いてもよい。
fun
というのは、関数を定義するキーワードです。JavaScriptで言うfunction
です。
printlnというのは、コマンド画面などに、文字表示をする組み込み関数のことです。[2]
printlnは、文末に改行が自動で入ます。[2]
JavaやC言語などと違い、文末にセミコロン (;
) は必要ありません。
その他の例[編集]
まずはコード例を見てみましょう。
- コード例
fun main() { var mes : String // (1) mes = "テストです" println(mes) var num : Int // (2) num = 5 println(num * 3) }
- 実行結果
テストです 15
- 解説
変数を宣言するときは、
var 変数名 : 型名
という書式で宣言します。
なお、Kotlin の型名はJava同様、大文字のキャメルケースであるのが通例です。
上記のコードの (1) では、mesというkotlin.String
型の変数を、 (2) では、numというkotlin.Int
型の変数を宣言しています。
val
を用いることもできるが、val
で宣言された場合は再度代入することができません。
- var は variable (変数)の略。
- val は value (値)の略。
文字表示と標準入出力の概要[編集]
文字の連結[編集]
println関数などで2個以上の文字列を連結したい場合は、+
演算子でつなぎあわせます。
- コード例
fun main() { var mes : String mes = "テストです" println(mes) var num : Int num = 5 println(num * 3) println(mes + num) }
- 実行結果
テストです 15 テストです5
※ 変数 num 自体の中身は15ではなく5。
式が要求されている箇所で、kotlin.String
でない型の変数にkotlin.String
型の変数を、あるいはkotlin.String
型の変数にkotlin.String
でない型の変数を足すと、自動的にkotlin.String
型でない変数をレシーバーとしてtoString()
が暗黙的に呼ばれ[3]、kotlin.String
型同士の演算となります。
二重引用符と一重引用符[編集]
println("出したい文字") などの文字列をくくる引用符は、kotlinでは二重引用符(ダブル クォーテーション、"
)のみが使用できます。
Javaと同じく、"a"
はa
という中身のkotlin.String
型のインスタンスとして認識されるが、'a'
はa
という中身のkotlin.Char
型のインスタンスとして認識されます。
テンプレートリテラル[編集]
kotlinではプラス記号+
で文字列を連結しなくても、下記のように文字列中に別の式を埋め込む事が出来ます。
- コード例
fun main() { var num : Int num = 5 println("数字は $num だ") // (1) }
- 実行結果
数字は 5 だ
- 解説
Kotlinではドル記号$
をつけて変数名を文字列の中に直接書くことができ、文字列テンプレート( string template ) と呼びます。
JavaScriptでは同様の機能を「テンプレート・リテラルのプレースホルダー」と呼ばれます。
なお、(1) は回りくどく書くと以下のような意味になります。
println("数字は" + num + "だ") // あるいは println("数字は" + num.toString() + "だ")
ドル記号 $ をエスケープする [編集]
$
そのものを文字表示したい場合は、\$num
のように、バックスラッシュ「\」をドル記号の前につけると、直後の文字を単なる文字として認識します(他の多くの言語のように $$
ではありません)。
これをエスケープ・シーケンスと言います。C言語など多くの言語に、類似の仕組みがあります。(なお、Windows日本語版の場合、バージョンによってはバックスラッシュが円通貨マークで表示される場合もあります。)
- コード例
fun main() { var num : Int num = 5 println("\$num は $num だ") }
- 実行結果
$num は 5 だ
標準入力[編集]
コンソール、ターミナル、DOSプロンプトなどから入ってくるデータをstdin、またはw:標準入力と呼ぶ。 ヒューマンフレンドリに言うならば、それらのウィンドウに入力した文字列やパイプで流した文字列を標準入力と呼ぶ。
Kotlin で同じことを行うためには、readLine()
関数を使う。
- コード例
fun main() { println("文字を入力してください") var input = readLine() println(input + "とあなたは入力しました") }
- 実行結果の例
- ※ 入力内容によって結果が異なります。下記は hhh と入力した例。
文字を入力してください hhh hhhとあなたは入力しました
なお、Javaでは、以下のようにボイラープレートコード( boilerplate code )を都度書く必要があった:
- Java の場合
import java.util.Scanner; /// Scanner scanner = new Scanner(System.in); String line = scanner.nextLine();
これはScanner
コンストラクターにJavaの「標準」入力ストリームSystem.in
を渡し、ScanneのnextLine()メソッドの戻値を変数line
に格納する。
条件分岐[編集]
if[編集]
if式は、条件式に基づき分岐し、分岐先の式を評価します。 if式の値は、分岐先の式の値です。 if式の値を右辺値化した場合、else節は必須です。
- 構文
if-expr := if '(' 条件式 ')' 式1 [ else 式2 ]
- 条件式に整数を使うと
fun main(args: Array<String>) { val i = 0 if (i) println("zero") }
- コンパイルエラー
Main.kt:4:9: error: type mismatch: inferred type is Int but Boolean was expected if (i) ^
- Kotlinでは、if式に限らず、条件式は、Boolean 型でなければいけません。
- if式の例
fun main(args: Array<String>) { val i = 0 if (i == 0) println("zero") else println("non zero") println( if (i == 0) "Zero" else "Non zero" ) }
- 実行結果
zero Zero
when[編集]
when式を使って条件分岐をすることができます。 when 式は、when に与えられた式に最初にマッチするパターンに結ぶ付いた値を返すパターンマッチングです。 式が省略されると、パターンの条件が最初に真になるパターンに結びついた値を返します。
- when式の例
fun main() { val mes = "a" when (mes) { "a" -> { print("定数で初期化しているので、") println("mesはa") } "b" -> println("mesはb") "c", "d", "e" -> println("mesはcかdかe") else -> println("mesはそれ以外") } // when 式の値を使った等価なコード println(when (mes) { "a" -> { print("定数で初期化しているので、") "mesはa" } "b" -> "mesはb" "c", "d", "e" -> "mesはcかdかe" else -> "mesはそれ以外" }) }
- 実行結果
定数で初期化しているので、mesはa 定数で初期化しているので、mesはa
- when式は、いくつかの論理条件に応じて、複数の異なる制御構造体(ケース)のうちの1つを評価することができるという点で、条件式と類似しています[4]。
- 重要な違いは、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式の条件の構文
値 値1, 値2... , 値n in 範囲式 !in 範囲式 is 型 !is 型 else
- 値は、定数である必要はなくメソッドでも構いません。
- 式は、単式i外にブロック式
{...}
でも構いません。
[TODO:境界値を省略した例、when文でループを脱出する例、enumな式が境界値に与えられた例]
繰り返し処理[編集]
Kotlinは、while と for の2つの繰り返し構文があります。
while式[編集]
while式は、条件式が true の間、式を評価しつづけます。 while式の値は、評価した式の値です。
- 構文
while-expr := 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
for式とコレクション[編集]
Kotlinのfor式はw:foreach文タイプのループ構文で、C言語の for(;;) とは異なる構文です。
- 構文
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(ループ変数) }
- このことから、コレクションは .iterator(), .hasNext(), .next() の3つのメソッドを持つクラスと規定できます(この様なメソッド集合をプロトコルといい、この場合は for プロトコルといいます)。
コレクション[編集]
println((1..5).javaClass.kotlin)
の結果が示す通り、範囲リテラル1..5
はclass kotlin.ranges.IntRange
です。
コレクションは、Ranges以外にも、Sequences・Ranges・Lists・Arrays・Sets・Mapsなどがあります。これは網羅していませんし、上記の forプロトコルに従ったクラスを作れば、ユーザー定義のコレクションも作成できます。
- 様々なコレクション
fun main(args: Array<String>) { val collections = arrayOf( 1..5, 1..8 step 2, 5 downTo 1, 8 downTo 1 step 2, 'A'..'Z', listOf(2,3,5), setOf(7,11,13)) println("$collections(${collections.javaClass.kotlin})") for (collection in collections) { print(collection) print(": ") for (x in collection) { print(x) print(" ") } print(": ") println(collection.javaClass.kotlin) } }
- 実行結果
class kotlin.Array 1..5: 1 2 3 4 5 : class kotlin.ranges.IntRange 1..7 step 2: 1 3 5 7 : class kotlin.ranges.IntProgression 5 downTo 1 step 1: 5 4 3 2 1 : class kotlin.ranges.IntProgression 8 downTo 2 step 2: 8 6 4 2 : class kotlin.ranges.IntProgression A..Z: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z : class kotlin.ranges.CharRange [2, 3, 5]: 2 3 5 : class java.util.Arrays$ArrayList [7, 11, 13]: 7 11 13 : class java.util.LinkedHashSet
- 二重のforループで、外周はコレクションのコレクションで、内周は個々のコレクションの要素をイテレーションしています。
repeat関数[編集]
Kotlinにはrepeat関数があり、定数回の繰返しが必要な時に便利です[5]。
- repeat関数
fun main() { repeat(5) { println("it = $it") } repeat(3) { i -> repeat(4) { j -> println("(i, j) = ($i, $j)") } } }
- 実行結果
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) (i, j) = (1, 3) (i, j) = (2, 0) (i, j) = (2, 1) (i, j) = (2, 2) (i, j) = (2, 3)
- itは、暗黙のループ変数です。
- 多重ループでは、ループ変数の名前が固定では都合が悪いので、ブロックの先頭で
識別子名 ->
とすることで明示的に名前をつけることができます。
ブロックを受取る関数[編集]
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つを取るので、名付けが必要です。
このように、ブロックを取るメソッドを使うとコレクションに関する操作を簡素に書けます。
識別子の重複とシャドーイング[編集]
Kotlinでは、内側のスコープの識別子と外側のスコープの識別子が重複した場合、内側のスコープの識別子が参照されます。
- コード例
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)") }
- 行列式を扱っていると、よくやらかします。
クラス[編集]
Kotlinは、関数型プログラミング言語であると同時に、オブジェクト指向プログラミング言語です。 より厳密に言うと、(プロトタイプベースではなく)クラスベースのオブジェクト指向プログラミング言語です。 クラス(class)は、オブジェクトを作る雛形で、クラスからコンストラクタを使ってオブジェクトを作ることをインスタンス化、出来たオブジェクトの事をインスタンスと呼びます。
クラス定義とインスタンス化とメソッド[編集]
- コード例
fun main(args: Array<String>) { class Hello(val s: String = "world") { override fun toString(): String { return "Hello $s!" } fun print(): Unit { println(s) } } val hello1 = Hello() println(hello1) hello1.print() val hello2 = Hello("my friend") println(hello2); print( """ Hello::class.java === ${Hello::class.java} hello1 === ${hello1} hello2.s = ${hello2.s} """ ) }
- 実行結果
Hello world! world Hello my friend! Hello::class.java === class MainKt$main$Hello hello1 === Hello world! hello2.s = my friend
- Ruby#クラスの例を、Kotlin に移植しました。
- 冒頭4行がクラス定義です。
- クラス定義に、他のオブジェクト指向言語ならコンストラクタに渡すような引数が渡されています。
- メンバーを公開するケースなら、この様に宣言的な引数リストを使うとメンバー定義と暗黙の初期値を与えられます。
- toString は、オブジェクトを文字列化するメソッドで、Objectの同名のメソッドをオーバーライドしています。
- print は、このクラスに独自なメソッドで、println() の値 == () == Unit を戻値型としています。
演算子オーバーロード[編集]
Kotlinでは、演算子はメソッド形式の別名を持ちます。
例えば、a + b
は a.plus(b)
とも書けます。
この plus メソッドを再定義すると、演算子オーバーロードができます[6]。
- コード例
fun main(args: Array<String>) { class Point(val x : Int = 0, val y : Int = 0) { override fun toString(): String { return "Point(x=$x, y=$y)" } } operator fun Point.plus(other: Point): Point { return Point(x + other.x, y + other.y) } val p1 = Point(15, 25) val p2 = Point(20, 30) println(p1 + p2) println(p1.plus(p2)) println(12 + 5) println(12.plus(5)) }
- 実行結果
Point(x=35, y=55) Point(x=35, y=55) 17 17
演算子は、もちろん加算だけではありません。
単項演算子 式 メソッド形式 +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 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 1.3以降: クラスの中にないmain関数はargs変数を宣言しなくても良い。
出典[編集]
- ^ https://kotlinlang.org/docs/reference/whatsnew13.html
- ^ 2.0 2.1 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/println.html
- ^ https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/to-string.html
- ^ Kotlin language specification Chapter 8 Expressions 8.6 When expressions
- ^ repeat - Kotlin Programming Language
- ^ Operator overloading