はじめに
Kotlin Referenceの日本語訳。複数の記事に分けていて、この記事はBasic章に相当する。
一気にざっと文法全体を確認したい場合は「30分で覚えるKotlin文法」を用意したのでそちらをどうぞ。
英語をそのまま訳したものはどうしても分かりにくくなりがち。ここでは公式リファレンスそのままにこだわってなくて、内容を理解した上で適宜表現を変えたり補足したり省略したりしている。
他の人の記事で「Kotlinの公式リファレンスを日本語化してみた[前編]」っていうのもあって、最初のGetting Started章から訳されてる。
目次
複数の記事に分けているのでここに全体像のリンクを貼っておく。
- 基本 (この記事)
- クラスとオブジェクト
- 関数とラムダ
- その他
- Javaとの相互運用
Nullableについて
この章より後で説明される Nullable というのが説明に出てくる。Kotlinでは変数に null は入れられないので、null か参照を入れられる特別な Nullable というものが用意されている。
Int の参照か null が入れられる Nullable は Int? と記述する。その他の構文は後ほど。構文も含めてSwiftでのOptionalと同様のもの。
基本型
Kotlinでは全てがプロパティやメソッドを持つオブジェクト。実際には速度的な観点からビルトイン型もあるが、それらも利用者からは普通のクラスのように見える。
数値
数値の扱いはJavaに近いが全く同じではない。数値は暗黙の型変換が行われず、記法も少し違う場合がある。
型 | ビット数 |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
数値定数
- 10進数: 123
- Longなら後ろにLをつける: 123L
- 16進数: 0x0F
- 2進数: 0b00001011
(8進数はサポートされていない)
- デフォルトではDouble: 123.5, 123.5e10
- Floatは後ろにfかFをつける: 123.5f
表現
Int? などのNullableやジェネリクスでは、数値はboxingされる。
val a: Int = 10000 // これはJVMのプリミティブ型として格納される
print(a === a) // 'true'と表示される
val boxedA: Int? = a // boxingされる
val anotherBoxedA: Int? = a // boxingされる
print(boxedA === anotherBoxedA) // !!!'false'と表示される!!!
// 内部の値の等価性は保持される
print(boxedA == anotherBoxedA) // 'true'と表示される
(注: ===は参照の等価性, ==は構造の等価性の比較。つまり==はJavaでいうequalsメソッドによる比較。)
明示的な型変換
小さい型は大きい型のサブ型ってわけじゃない。もしそうならこんな感じで問題が発生する。
// もし〜だったら的な仮想コード。コンパイルできない。
val a: Int? = 1 // Int (java.lang.Integer)にBoxingされる
val b: Long? = a // 暗黙変換で Long (java.lang.Long)にBoxingされる
print(a == b) // ちょwwwこいつ"false"とか言うwww
// Longのequals()がもう一方の型もLongかチェックするからなんですね〜
そんなわけで小さい型は大きい方に暗黙的に変換されない。
// 1はIntだけど変数型がByteなのでコンパイル時にByte型の数値として扱われる
val b: Byte = 1 // OK, 数値は静的にチェックされる
// Byte型の値をInt型に格納しても暗黙型変換はされない
val i: Int = b // ERROR
val i: Int = b.toInt() // OK: 明示的に型変換
全ての数値型は次の型変換メソッドを持っている。
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
暗黙の型変換がないことは型推論と算術演算子のオーバーロードによりほとんど気にならない。
// a + b では a.plus(b) が呼ばれる。LongがIntを受け取るplusメソッドを用意している。
val l = 1L + 3 // Long + Int => Long
演算子
数値に対して標準的な算術演算子が利用できる。演算子は実際には適切なクラスのメンバが呼び出される1。詳細は演算子オーバーロードの章で説明する。
ビット演算には特別な記号は割り当てられていないが、次のように挿入形式で使える関数がある。
// shlは算術左シフトを行う
val x = (1 shl 2) and 0x000FF000
IntとLongには以下のビット演算がある。
- shl(bits) – 符号付左シフト (Javaの <<)
- shr(bits) – 符号付右シフト (Javaの >>)
- ushr(bits) – 符号なし右シフト (Javaの >>>)
- and(bits) – ビットAND
- or(bits) – ビットOR
- xor(bits) – ビットXOR
- inv() – ビット反転
文字
文字型はChar、もうわかってると思うけど暗黙の型変換はない。
fun check(c: Char) {
if (c == 1) { // ERROR: 暗黙の型変換
// ...
}
}
リテラルはシングルクォートで囲む '1', '\n', '\uFF00' みたいに。明示的にInt型に変換できる。
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // 明示的に数値に変換
}
数値と同様に Nullable ではBoxingされる。
真偽値
Boolean は true か false を取る。Nullable ではBoxingされる。3つの組み込み演算子 * ||, && , !* がある。
配列
Arrayクラスで表される。get/setメソッドがあり([]でアクセスできる)、sizeを持ち、他に幾つかの便利メソッドを持つ。
class Array<T> private constructor() {
val size: Int
fun get(index: Int): T
fun set(index: Int, value: T): Unit
fun iterator(): Iterator<T>
// ...
}
// [1, 2, 3]を作る
val list = arrayOf(1, 2, 3)
// nullで埋められたサイズ3の配列を作る
var arr: Array<Int?> = arrayOfNulls(3)
// Array<String>型で値が ["0", "1", "4", "9", "16"]の配列を作る
val asc = Array(5, { i -> (i * i).toString() })
注: Javaと違って Array<String> 型を Array<Any> 型に代入はできない2。これは実行時エラーの発生を防いでくれる。(ただし Array<out Any> を利用できる。Type Projectionの章を参照。)
Boxingのオーバーヘッドを生じないプリミティブ型の配列を表す特別なクラスを用意している。ByteArray, ShortArray, IntArrayなど。これらはArrayを継承してはいないが、同じプロパティとメソッドを持っている。これらも対応するファクトリ関数を持っている。
// プリミティブ型の配列である IntArray を用いる
val x: IntArray = intArrayOf(1, 2, 3) // 型を明示したが、推論してくれるので省略可能
x[0] = x[1] + x[2]
文字列
文字列はString型で表され、immutable(中身が変更不可)である。構成要素は文字であり、s[i]という形でインデックスを指定してアクセスできる。forループでイテレートできる。
for (c in str) {
println(c)
}
文字列リテラル
// Javaの文字列と同じ感じのやつ
val s = "Hello, world!\n"
// トリプルクォートを使うとスケープが効かない生文字列になる
val text = """
for (c in "foo")
print(c)
"""
文字列テンプレート
$を使って文字列の中に変数の値や計算結果を埋め込める。
val i = 10
val s = "i = $i" // i = 10
val s = "abc"
val str = "$s.length is ${s.length}" // abc.length is 3
// $そのものを表現するには
val price = "${'$'}9.99" // $9.99
パッケージ
// 所属するパッケージ名を指定
package foo.bar
// フルネームはfoo.bar.baz
fun baz() {}
// フルネームはfoo.bar.Goo
class Goo {}
// ...
インポート
import foo.Bar // これでBarにアクセスできる
import foo.* // fooの下にあるものが全てアクセスできる
import foo.Bar // Barにアクセスできる
import bar.Bar as bBar // bBarは'bar.Bar'を表す
クラス以外にも以下をimportできる。
- トップレベルの関数やプロパティ
- object宣言の中の関数やプロパティ
- enum定数
Javaと違って特別に"import static"というのはない。通常のimportでいける。
制御構文
if
Kotlinではifは式であり戻り値を返せる。なので三項演算子は必要ないので存在しない。
// 伝統的な使い方
var max = a
if (a < b)
max = b
// else付き
var max: Int
if (a > b)
max = a
else
max = b
// ifが式であることを利用した三項演算子的使い方
val max = if (a > b) a else b
分岐はブロックにできる。最後の行の式の評価結果がブロックの値になる。
val max = if (a > b) {
print("Choose a")
a
}
else {
print("Choose b")
b
}
ifの結果を利用するならelse節が必要になる。
when
whenはC系言語でいうswitchの置き換え。
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
// どれにも当てはまらないならelseが実行される
else -> { // ブロックも使える
print("x is neither 1 nor 2")
}
}
whenも式として値を返せる。その場合else節は必須で、全ての分岐が値を返す必要がある。ブロックが複数行ならifと同様に最後に評価された値を返す。
when (x) {
// 0と1で同じ処理なら、コンマで結合できる
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
when (x) {
// 定数でなくてもマッチさせられる
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
when (x) {
// 範囲に含まれるか
in 1..10 -> print("x is in the range")
// 配列などのコレクションに含まれるか
in validNumbers -> print("x is valid")
// 否定も使える。範囲に含まれなければ実行される。
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
val hasPrefix = when(x) {
// 型チェックもできる。チェック後は自動的にキャストされる。
is String -> x.startsWith("prefix") // xはStringとして扱える
else -> false
}
whenに引数を与えないと、ifの代わりに利用できる。各条件は単純に真偽値判定される。
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
for
// まぁこれは説明いらないよね
for (item in collection)
print(item)
// もちろんボディはブロックにできる
for (item: Int in ints) {
// ...
}
for はイテレータを提供するものなら何でもイテレートできる。すなわち、
- メンバーまたは拡張として iterator() を持ち、その戻り値の型が
- メンバーまたは拡張として next() を持ち、かつ
- メンバーまたは拡張として Boolean を返す hasNext() を持つ
これら3つ全ての関数は operator としてマークされている必要がある。
インデックス付きにしたいなら
// 最適化によってindicesは実際にはオブジェクトを生成しないのでパフォーマンス劣化はない
for (i in array.indices)
print(array[i])
// withIndex()でインデックスと値のペアで回すこともできる
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
while
while, do-whileは普通。
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // ここでyが見える!
リターンとジャンプ
3つのジャンプ命令がある。
- return : デフォルトでは直近の(無名を含む)関数を抜ける。注:ラムダは違う。
- break : 直近のループを抜ける
- continue : 直近のループの次のステップに進む
break, continueとラベル
// ラベルを使って直近でなく指定ラベルのループを抜ける
loop@ for (i in 1..100) {
for (j in 1..100) {
if (...)
break@loop
}
}
returnとラベル
return は直近の関数を抜ける。ラムダじゃない。
fun foo() {
ints.forEach {
if (it == 0) return // これはforEachでなくfooを抜ける
print(it)
}
}
もしラムダを抜けたいならラベルを使う。
fun foo() {
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
}
この程度でラベルを定義する必要はなく、暗黙のラベル(ラムダが渡される関数の名前)を使った方が便利。
fun foo() {
ints.forEach {
if (it == 0) return@forEach // foo関数ではなくforEachに渡したラムダを抜ける(処理1回分)
print(it)
}
}
またはラムダじゃなく無名関数を渡す。
fun foo() {
ints.forEach(fun(value: Int) {
if (value == 0) return // foo関数ではなくforEachに渡した無名関数を抜ける(処理1回分)
print(value)
})
}
ラベルを使いつつ値を返したいならこう書く。
return@a 1
次の章へ
次はKotlin文法 - クラス、継承、プロパティへGO!