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

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


Kotlin(コトリン)は、クロスプラットフォームで、静的型付けな、型推論を用いることが出来る汎用プログラミング言語です。 Kotlinは、関数型プログラミング・クラス指向のオブジェクト指向型プログラミング・シェネリックプログラミングなど、複数のプログラミングパラダイムをサポートするマルチパラダイム言語です。 Kotlinは、Javaと完全に相互運用できるように設計されており、JVM版のKotlinの標準ライブラリはJavaクラスライブラリを利用しているが、型推論によってより簡潔な構文にすることが可能です。 Kotlinは、主にJVMをターゲットとしているが、JavaScript(Reactを用いたフロントエンドのWebアプリケーションなど)やLLVMによるネイティブコード(Androidアプリとビジネスロジックを共有するiOSネイティブアプリなど)にもコンパイルできます。 言語開発のコストは、JetBrainsが負担し、Kotlin Foundationが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!")
}

このケースについてはさらに短くでき、[注 1][1]

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("non zero")
}
コンパイルエラー
Main.kt:4:9: error: type mismatch: inferred type is Int but Boolean was expected
    if (i) 
        ^
Kotlinでは、if式に限らず、条件式は、Boolean 型でなければいけません。
    if (i != 0)
        println("non zero")
とします。
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..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関数があり、定数回の繰返しが必要な時に便利です[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 + ba.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


注釈編集

  1. ^ Kotlin 1.3以降: クラスの中にないmain関数はargs変数を宣言しなくても良い。

出典編集

  1. ^ https://kotlinlang.org/docs/reference/whatsnew13.html
  2. ^ 2.0 2.1 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/println.html
  3. ^ https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/to-string.html
  4. ^ Kotlin language specification Chapter 8 Expressions 8.6 When expressions
  5. ^ repeat - Kotlin Programming Language
  6. ^ Operator overloading

外部リンク編集