Help us understand the problem. What is going on with this article?

Kotlin文法 - 基本

はじめに

Kotlin Referenceの日本語訳。複数の記事に分けていて、この記事はBasic章に相当する。

一気にざっと文法全体を確認したい場合は「30分で覚えるKotlin文法」を用意したのでそちらをどうぞ。

英語をそのまま訳したものはどうしても分かりにくくなりがち。ここでは公式リファレンスそのままにこだわってなくて、内容を理解した上で適宜表現を変えたり補足したり省略したりしている。

他の人の記事で「Kotlinの公式リファレンスを日本語化してみた[前編]」っていうのもあって、最初のGetting Started章から訳されてる。

目次

複数の記事に分けているのでここに全体像のリンクを貼っておく。

Nullableについて

この章より後で説明される Nullable というのが説明に出てくる。Kotlinでは変数に null は入れられないので、null か参照を入れられる特別な Nullable というものが用意されている。

Int の参照か null が入れられる NullableInt? と記述する。その他の構文は後ほど。構文も含めて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される。

真偽値

Booleantruefalse を取る。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!


  1. コンパイラが最適化のために対応する命令に書き下すことはある。 

  2. 元の文章ではinvariantと記載されている。これは共変性と反変性の説明によるところのinvariantということ。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした