はじめに
Kotlinの文法を短時間でざっと確認することが目的。最初はどんな言語なのか外観を掴む感じで全体に目を通して、書いたり読んだりしながら必要なところを見直すのが良いんじゃないかと。
汎用的なライブラリではなく、アプリケーションを書く際に必要な項目を重視して、リファレンスの内容をコンパクトにまとめた1。網羅性はかなりのものだと思うが、なんじゃこれってのが出てきたら、その時にリファレンスで詳細を調べればいい。
細かい文法についてはリファレンスを元に日本語で解説した記事を用意したのでそちらを参照。
基本的にJavaを知っていることが前提だけど、他のオブジェクト指向言語を知っていれば理解できると思う。
変数
// Kotlinでは文の後ろにセミコロンはいらない
// 頭にvalって付けると変更不可
val num = 3 // 3はInt型なので、numもInt型だとコンパイラが推論してくれる
// 頭にvarって付けると変更可能。変更が必要でない限りvalを優先して使うと幸せになれる。
var i = 0
i = 1
// 明示的に型を指定するにはこう書く
val a: Byte = 1 // 1はInt型だけど、この場合はコンパイラがByte型とみなして代入してくれる
基本型
下の基本型があるが、Javaと違ってプリミティブ型は用意されていない。全て普通のクラスのように扱える。String以外は、Boxing2する必要がない場合はコンパイラがプリミティブ型に最適化してくれる。
型 | 説明 |
---|---|
Double | 64ビット浮動小数点数 |
Float | 32ビット浮動小数点数 |
Long | 64ビット符号付き整数 |
Int | 32ビット符号付き整数 |
Short | 16ビット符号付き整数 |
Byte | 8ビット符号付き整数 |
Char | 1文字を表す文字型 |
Boolean | 真偽値(trueまたはfalse) |
String | 文字列 |
数値定数
val digits = 123 // 10進数の数値定数はこう
val longInt = 123L // Longなら後ろにLを付ける。これでlongIntはLong型と推論される
val hex = 0x0F // 16進数
val bin = 0b00001011 // 2進数
val dbl = 123.5 // 浮動小数点数はデフォルトでDouble型
val withE = 123.5e10 // 123.5 * 10^10
val flt = 123.5f // Floatなら後ろにfかFを付ける
文字と文字列
// 1文字のChar型を表すにはシングルクォートを使う
val c = '0'
val n = '\n'
val u = '\uFF00'
// Javaと同じ感じでダブルクォートの文字列が使える
val s = "Hello, world!\n"
// トリプルクォートを使うとエスケープが効かない生文字列になる
val text = """
改行も含めてここに書いた通りの文字列になる。
もちろんインデントも。
"""
// $を使って文字列の中に変数の値や計算結果を埋め込める。トリプルクォートでも使える。
// $そのものを表示したいときは${'$'}って書く
val i = 10
val str = "i = $i, i x 2 = ${i * 2}" // "i = 10, i x 2 = 20"
// 文字列(String)はimmutable(中身変更不可)で構成要素は文字(Char)
val c0 = str[0] // 配列のように[]で指定位置の文字を取得できる
for (chr in str) { // forループでイテレートできる
println(chr)
}
暗黙の型変換はない
// 整数の数値定数代入するときはコンパイラが判断してくれる場合もある
val a: Byte = 1 // 1はIntだけどByteとみなしてくれる。そもそも数値定数にByteを指定する方法がない。
val l: Long = 1 // これもOK
val i: Int = 1L // ERROR!! 明示的にLong型って指定するとエラーになる
val f: Float = 3.0 // ERROR!! 浮動小数点数だとダメ。Doubleとみなされる。
val d: Double = 3 // ERROR!! 整数を浮動小数点数とみなしてはくれない
// 小さい整数型を大きい整数型に入れられそうだけど・・・それはできない
val b: Int = a // ERROR!! Int型変数にByte型を代入しようとした。
// 型は明示的に変換する必要がある
val c: Int = a.toInt() // OK. Byte型を明示的にInt型に変換して代入。
// 全ての数値型が互いへの型変換メソッドを持っている。
// toByte() toShort() toInt() toLong() toFloat() toDouble() toChar()
// でも型推論や演算子オーバーロードのおかげで面倒だと感じることは少ない。
val n = 1L + 3 // LongとIntを取る+演算が呼び出される
ビット演算
Int型には以下のビット演算メソッドが用意されている。引数を取るものは後述する接中辞(infix)記法が使えるようになっている。
関数 | 意味 |
---|---|
shl(bits) | 符号付左シフト (Javaの <<) |
shr(bits) | 符号付右シフト (Javaの >>) |
ushr(bits) | 符号なし右シフト (Javaの >>>) |
and(bits) | ビットAND |
or(bits) | ビットOR |
xor(bits) | ビットXOR |
inv() | ビット反転 |
val x = (1 shl 2) and 0x000FF000
val y = 1.shl(2).and(0x000FF000) // 上はこれと同じ
val z = x.inv()
Nullable
KotlinはNullPointerException(略してNPE)撲滅運動中!
普通の型はnullを入れられない。nullを入れるにはNullableという特別な型を使う。普通の型の後ろに ? を付けるとNullableになる。
// 普通のString型はnullを入れられない
var a: String = "abc"
a = null // !!コンパイルエラー
val l = a.length // OK. aは絶対nullでないのでNPEは起こらない。
// NullableなString?型ならnullを入れられる
var b: String? = "abc"
b = null // ok
val l = b.length // !!コンパイルエラー: 変数 'b' は null がありうる
// Nullableな変数のプロパティやメソッドを呼び出すにはnullチェックする。
// チェックと呼び出しの間で変数の参照先が変化しないと保証されているなら、
// Nullableでない型に勝手にキャストしてくれる。
val l = if (b != null) b.length else -1 // b.length が呼び出せる
// if文の条件式の中でも、 && や || の前のチェックでnullでないと分かっていれば後ろではキャストされる。
if (b != null && b.length > 0) // if文の中でb.lengthが呼び出せる
print("String of length ${b.length}") // 勿論ここでも呼び出せる
// こんな風にも書ける。
val len = b?.length // ?を使うと「nullでなければ実行、そうでなければnullを返す」
// lenはnullがありうるので型はInt?と推論される。
// これ使うとこんな風にチェインして書ける。
val longLen = b?.length?.toLong()
// さっきの l の式は、ifじゃなくエルビス演算子 ?: を使ってこうも書ける。
// ?: の前がnullじゃなかったらそっちを、nullだったら後ろを返す。
val blen = b?.length ?: -1
Nullableに入れるとプリミティブ型として扱われていたものはBoxingされる。
val a = 10000 // JVMのプリミティブ型(int)として格納される
val b = a // これもプリミティブ型(int)
// === 演算子は参照先が同じかどうかをチェックする
print(a === b) // 'true'って表示される
val boxedA: Int? = a // boxingされる
val anotherBoxedA: Int? = a // boxingされる
print(boxedA === anotherBoxedA) // 'false'と表示される!!(この2つは別のものを参照している)
配列
配列はArray<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() })
// for文で回したり
for (item in list) { println(item) }
// []で要素にアクセスしたり
list[0] = list[1] + list[2] // Arrayはmutable(中身を変更可能)
Boxingされないプリミティブ型の配列を表す ByteArray, ShortArray, IntArray といったクラスが用意されている(Arrayを継承はしていないけど、同じプロパティとメソッドがある)。これらの配列を作るには専用の関数を使う。
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
コレクション
Kotlin の Array は Java の配列に相当するもの。kotlin.collections パッケージに List, Map, Set などのデータの集合を扱うインターフェースが用意されている。これらは readonly(中身を変更するメソッドがない)で、それぞれ中身を変更するメソッドを足した MutableList, MutableMap, MutableSet が用意されている。詳細はリファレンスを参照。
// List<Int> を作る。Listは中身を変更できない。
val list = listOf(1, 2, 3, 4, 5)
list[0] = list[1] + list[2] // ERROR!! 中身を変更できない
// MutableList<Int> を作る。こっちは中身を変更可能
val mlist = mutableListOf(1, 2, 3, 4, 5)
mlist[0] = mlist[1] + mlist[2] // OK
// MutableList<Int> から List<Int> アップキャスト可能
// (クラス内のprivateな MutableList を外部に List として公開するとかできる)
val rolist: List<Int> = mlist;
rolist[0] = rolist[1] + rolist[2] // ERROR!! 中身を変更できない
// 参照先は同じなので mlist 経由では中身を変更できる
// Map<Int, String> を作る。
val map = mapOf(1 to "A", 2 to "B")
println(map[2]) // "B" と表示される
map[1] = "C" // ERROR!! 中身を変更できない
// MutableMap<Int, String> を作る
val mmap = mutableMapOf(1 to "A", 2 to "B")
mmap[1] = "C" // OK
等価性
// 参照先が同じかどうかをチェックするには === 演算子かその否定の !== 演算子を使う
if (a === b && c !== d) {
// ...
}
// 構造の等価性(equalsメソッドによる同一性チェック)には == かその否定の != を使う
if (a == b && c != d) {
// ...
}
null との比較の場合、自動的に参照のチェックに変換される。つまりa == null は自動的に a === null になる。
制御構文
if
// 普通にif文が使える。
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// if文は式。値を返せる。だから三項演算子はない。
val maxVal = if (a > b) a else b
// 複数行ある場合、最後の行の評価結果が値になる
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
when
// CやJavaのswitchの置き換え。breakはいらない。
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
// どれにも当てはまらないならelseが実行される
else -> { // ブロックも使える
print("x is neither 1 nor 2")
}
}
// switchより遥かに強力
when (x) {
// 複数の値をコンマで区切って指定。ここでは「0または1」。
0, 1 -> print("x == 0 or x == 1")
// 定数でなくてもマッチさせられる
parseInt(s) -> print("s encodes x")
// 範囲に含まれるか
in 2..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")
}
// whenもifと同様に式であり、値を返せる。
val hasPrefix = when(x) {
// 型チェックもできる。チェック後は自動的にキャストされる。
is String -> x.startsWith("prefix") // xはStringとして扱える
else -> false
}
// 引数を与えなければif文の代わりに使える
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
for
// collection内の各アイテムをitemとしてループ
for (item in collection) {
print(item)
}
// インデックス付きにしたいなら
for (i in array.indices) {
print(array[i])
}
// withIndex()でインデックスと値のペアで回すこともできる
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
// マップをキーと値のペアで回す
for ((key, value) in map) {
println("key=$key, value=$value")
}
while
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // ここでyが見える!
範囲
if (i in 1..10) { // 1 <= i && i <= 10 と等価
println(i)
}
// 整数型の範囲(IntRange, LongRange, CharRange)はイテレート可能
for (i in 1..4) print(i) // "1234"と表示される
// 逆順
for (i in 4 downTo 1) print(i) // "4321"と表示される
// ステップ量を変える(ステップ量は常に正)
for (i in 1..4 step 2) print(i) // "13"と表示される
for (i in 4 downTo 1 step 2) print(i) // "42"と表示される
パッケージ
// 所属するパッケージ名を指定
package foo.bar
import hoge.Cat // これでhoge.Catにアクセスできる
import hoge.* // hogeの下にあるものが全てアクセスできる
import hoge.Dog as hDog // hDogはhoge.Dogを表す
// import static はない。普通のimportでいい。
関数
// 関数はパッケージのトップレベルで宣言できる(クラスメソッドにする必要はない)
// Int型の引数xを取り、Int型を返す関数 double を定義する。
fun double(x: Int): Int {
return x * 2
}
// 関数の中身が式1つだけなら波括弧は省略できる
fun double(x: Int): Int = x * 2
// 波括弧を省略した場合、戻り値の型が推論可能ならそれも省略できる。
fun double(x: Int) = x * 2
// 引数にはデフォルト値を指定できる(オーバーロードの数を減らせる)
// 戻り値が必要ない場合の型はUnitだが省略可能。
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
// ...
// return Unit じゃなく return でいいし、return も省略できる。
}
// デフォルト値を使って呼ぶ
reformat(str)
// デフォルト値を使わないで呼ぶ
reformat(str, true, true, false, '_')
// 引数名を指定して呼ぶ
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
// wordSeparatorだけデフォルト値を使わずに呼ぶ
reformat(str, wordSeparator = '_')
可変長引数
// varargを付けると可変長引数になる。引数はArrayとして渡される。
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts は Array である
result.add(t)
return result
}
// こんな風に可変個の引数を渡せる
val list = asList(1, 2, 3)
// 既に配列として持ってるものを渡す場合、* で展開する
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4) // *をつけて展開して渡す
ジャンプ命令とラベル
ジャンプ命令は return, break, continue の3つ。デフォルトと違う場所へジャンプしたいならラベルを指定する。ラムダや無名関数については後述する。
// ラベルを使って直近でなく指定ラベルのループを抜ける
loop@ for (i in 1..100) {
for (j in 1..100) {
if (i == 3 && j == 2) { continue@loop }
if (shouldBreak) { break@loop }
}
}
// returnは直近の"関数"を抜ける。ラムダを抜けるわけではない。
fun foo() {
// ラムダのreturnが関数fooを抜けることで、forEachメソッドが制御文のように使える
ints.forEach {
// 注: ラムダの中でreturnを書くにはインライン化される必要がある(forEachがそうなってる)
if (it == 0) return // これはforEachでなくfooを抜ける(returnがここにインライン展開される)
print(it)
}
}
// もしラムダを抜けたいならラベルを使う。
fun hee() {
// 以下のようにラベル名を自分で付けることもできるけど
// ints.forEach labelName@ {
// 自分でラベルを書かなくても、関数名がラベルとして使える
ints.forEach {
if (it == 0) return@forEach // forEachを抜ける
print(it)
}
}
// 無名関数だとreturnはデフォルトでその関数を抜ける
fun yoo() {
ints.forEach(fun(value: Int) {
if (value == 0) return // これはforEachを抜ける
print(value)
})
}
ラベルを使いつつ値を返したいときはこう書く。
return@a 1
クラスとインターフェース
コンストラクタ
// 中身がないクラス。コンストラクタを定義しないと、自動で引数なしコンストラクタが生成される。
class Empty
// 1つのプライマリコンストラクタと複数のセカンダリコンストラクタを持てる。
// プライマリコンストラクタの宣言はクラスヘッダに書く
class Customer(name: String) {
// 初期化処理はinitブロックの中に書く
init {
// プライマリコンストラクタの引数がこの中で使える
logger.info("Customer initialized with value ${name}")
}
// プライマリコンストラクタの引数がプロパティの初期化でも使える
val customerKey = name.toUpperCase()
// セカンダリコンストラクタ
constructor(firstName: String, lastName: String)
// プライマリがある場合、セカンダリは必ずプライマリを呼び出す
: this("$firstName $lastName") {
logger.info("firstName = $firstName, lastName = $lastName")
}
}
コンストラクタ引数に渡したものを、ただプロパティに代入するだけってことよくあるよね?
// コンストラクタの引数にval, varを指定できる。指定したものは自動でプロパティ化される。
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
// インスタンス生成にnewキーワードはいらない。コンストラクタを関数のように呼び出す。
val person = Person("Joe", "Smith", 30)
println(person.firstName) // firstNameはプロパティ化されてる
person.age++ // ageはvar指定したので変更可
継承
全てのクラスは Any を継承する。継承を指定しない場合、自動的に Any を継承したクラスになる。
// open付けないと継承できない。デフォルトではJavaでのfinal classになる。
open class Base(val p: Int) {
// デフォルトでfinal扱い。open付けないとオーバーライドできない。
open fun v() {}
// こっちはopen付けてないのでオーバーライドできない。
fun nv() {}
}
// コロンで継承。プライマリコンストラクタがあるならその場で基底クラスを初期化する。
class Derived(p: Int) : Base(p) {
// オーバーライドする側ではoverrideを付ける。
override fun v() {}
}
open class AnotherDerived(p: Int) : Base(p) {
// それ以上オーバーライドさせないならfinalを付ける。
final override fun v() {}
}
// abstractを付けると抽象クラスや抽象メソッドになり、明示しなくてもopen扱い。
abstract class Derived(p: Int) : Base(p) {
// abstractでないメソッドをabstractとしてオーバーライドすることもできる
override abstract fun f()
}
// プライマリコンストラクタがない場合
class MyView : View {
// 各セカンダリコンストラクタは親クラスのコンストラクタをsuperで呼び出す
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
}
}
メソッド
クラス内に宣言する以外は、宣言の仕方は関数と同じ。
プロパティ
Javaのようにフィールドをprivateで用意してgetter/setterを別に用意・・・とか面倒なことはしなくていい。プロパティの値が格納される領域(__バッキングフィールド__と呼ぶ)は必要なときだけ自動で生成される。
class MyClass {
var initialized = 1 // 型推論によりInt型、デフォルトのgetter/setter利用。
// カスタムgetter/setterを定義(この場合、バッキングフィールドは生成されない)
var stringRepresentation: String
get() = this.toString()
set(value) { // setterの引数は慣習でvalueだけど、好みで他の名前でもいい
setDataFromString(value) // 文字列をパースして他のプロパティに代入
}
var setterVisibility: String = "abc"
private set // private指定してsetterはこのクラス内のみ利用可。実装はデフォルトを利用
var setterWithAnnotation: Any? // 初期値指定なし
@Inject set // setterにInjectアノテーションを付ける。実装はデフォルトを利用。
// valだと当たり前だがsetterは定義できない
val simple: Int? // Int?型, デフォルトのgetter利用。初期値がないのでコンストラクタで初期化必須。
val inferredType = 1 // Int型、デフォルトのgetter利用
val isEmpty: Boolean // バッキングフィールドはない
get() = this.size == 0 // カスタムgetterを定義
// カスタムgetter/setterからはfieldを使ってバッキングフィールドにアクセスできる。
// fieldが使われていたらカスタムgetter/setterを使っていてもバッキングフィールドが生成される。
var counter = 0 // 初期値は直接バッキングフィールドに書き込まれる
set(value) {
if (value >= 0)
field = value // fieldを通してバッキングフィールドに値を格納する
}
}
プロパティと同じ書式でパッケージのトップレベルにグローバル変数を定義できる(できるだけ使わないことをオススメするけど)。
プロパティのオーバーライド
メソッドのオーバーライドとルールは同じ。
// openつけないと継承できない
open class Person(name: String) {
open val name: String // openつけないとoverrideできない
init { this.name = name }
}
class UpperCaseNamePerson(name: String) : Persion(name) {
// プロパティをoverrideしてgetterを変更
override val name: String
get() { name.toUpperCase() }
}
遅延初期化プロパティ
DIとかUnitテストとかのセットアップで、オブジェクト生成後に初期化する場合に使える。
// lateinitを付けると遅延初期化プロパティになる。varにしか使えない。
// カスタムgetter/setterは持てない。Nullableやプリミティブ型であってはいけない。
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject() // ここで初期化する
}
@Test fun test() {
// Nullableじゃないのでnullの可能性を考えずに普通に使える
// 初期化前にアクセスするとそれ用の例外が発生する
subject.method()
}
インターフェース
// abstract同様にオーバーライドするのが当たり前だから、openは付けなくていい
interface MyInterface {
fun bar()
fun foo() {
// インターフェースも(Java 8と同様に)デフォルト実装を持てる
}
}
// interfaceの実装
class Child : MyInterface {
override fun bar() {
// 実装
}
}
// interfaceもプロパティを持てる。
// ただし状態は持てないのでバッキングフィールドが生成されないものに限る。
interface MyInterface {
// バッキングフィールドはないので値は与えられない
val property: Int // abstract
// getterの実装を与えることはできる
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(property) // オーバーライドされる前提でプロパティにアクセスできる
}
}
class Child : MyInterface {
override val property: Int = 29 // abstractになってるpropetryをオーバーライド
}
アクセス制限
Javaとは違い、何も指定しなかった場合のデフォルトは public 。
package foo
// privateだとこのファイル内だけでしか見えない
private fun foo() {}
// publicなのでこのプロパティはどこからでも見える
public var bar: Int = 5
private set // setterはこのファイル内からしか見えない
// internalは同じモジュール内なら見える
internal val baz = 6
クラス内でのアクセス制限には protected が増えて、以下のようになる。
- private はそのクラス内だけでしか見えない
- protected はそのクラスとサブクラスからしか見えない(Javaと違う)
- internal は同じモジュール内でそのクラスが見えているなら見える(Javaと違う)
- public はそのクラスが見えているなら見える
// 今までのプライマリコンストラクタの書き方はconstructorを省略してきた。
// アノテーションとかアクセス指定をしたいならconstructor使う正規の書き方をする。
class Student public @Inject constructor(name: String) {
// ...
}
モジュールというのはパッケージとは違い、1つのコンパイル単位を表す。ようするにコンパイルされている最中だけ一緒にコンパイルされているファイル全体から見えるけど、コンパイルが終わると他からは見えなくなる。例えばライブラリをjarファイルにまとめるとき、一緒にコンパイルするライブラリ全体からは見えるけど、jarにしちゃったらそれを使う側からは見えない。
ネスト
// クラスはネストできる
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
// innerを付けると内部クラスになり、外側のクラスのprivateメンバにアクセスできる
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar // 外側のprivateなメンバにアクセスできる
}
}
val demo = Outer().Inner().foo() // == 1
// 関数もネストしてローカル関数を宣言できる。
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
// この関数の中で、外側にあるvisitedが見える。
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
enumクラス
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
// 各定数はenum classのインスタンスであり初期化できる
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
// 各定数はそれ用の無名クラスを宣言することができる
enum class ProtocolState {
// WAITINGはProtoclStateを継承してsignal()をオーバーライドした無名クラスのインスタンス
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
// ProtoclStateクラスのメンバがこの後に続くのでその前をセミコロンで区切る。
// これはJavaのenumと一緒。
abstract fun signal(): ProtocolState
}
enumクラスはJavaと同様に valueOf(), values() メソッドを持っている。また各定数は name, ordinal プロパティを持っている。
シールドクラス
enumクラスに似ているが、状態を持つことができ、複数のインスタンスを生成できる。
// sealedを付けられたクラスを継承できるのは、その内部クラスだけ
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
// whenと一緒に使うと便利
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 全てのケースをカバーしてるのでelseはいらない
// Exprを継承できるのは内部クラスだけなので、上で定義されてる分だけしかないことを
// コンパイラは知っている。
分解宣言(destructuring declaration)
実は既に for 文の例(Mapのイテレートなど)で使っている。
// personの中身をname, ageにバラして代入。これを分解宣言という。
val (name, age) = person
// 個別に使える
println(name)
println(age)
分解宣言は以下のコードにコンパイルされる。
val name = person.component1()
val age = person.component2()
もちろんcomponent3(), component4()と続けることができる。これらのcomponentN()関数は operator キーワードを付けて宣言しておく必要がある。
データクラス
単にデータを保持したいだけってクラスを簡単に作れる。
data class User(val name: String, val age: Int)
頭に data って付けると、コンパイラが以下のものを自動で作ってくれる。ただしクラス内または継承元に明示的に定義されていれば勝手に生成したりしない。
- equals()/hashCode()ペア
- "User(name=John, age=42)"って表示するtoString()
- 宣言順で内容を取り出すcomponentN()関数
- copy()
こんな感じで便利に使える。
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2) // nameはそのままでageだけ変更したコピーを生成
val (name, age) = jack // 中身をバラして取り出す
println("$name, $age years of age") // "Jack, 1 years of age"って表示される
オブジェクト
オブジェクト式(無名クラスのインスタンス生成)
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
// MouseAdapterを継承した無名クラスのオブジェクトを作ってaddMouseListenerに渡す
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++ // この無名クラスの外側にあるclickCountにアクセスできる。
// しかもvalじゃなくてvarであっても。
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// ...
}
// 何も継承を指定しない(Anyのみ継承する)オブジェクトも作れる
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
メソッド1つだけのインターフェースを受け取る関数には、後述するSAM変換を利用してラムダを渡す方法を使ったほうがいい。
オブジェクト宣言(シングルトン)
// 簡単にシングルトンが作れる
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
// 使い方
DataProviderManager.registerDataProvider(dataProvider)
コンパニオンオブジェクト
Kotlinにstaticメンバはない。代わりにコンパニオンオブジェクトが使える。
class MyClass {
// companionを付けるとコンパニオンオブジェクトになる
companion object Factory {
fun create(): MyClass = MyClass()
}
}
// コンパニオンオブジェクトのメンバーはオブジェクト名を指定しないで呼びだせる。
val instance = MyClass.create()
interface Factory<T> {
fun create(): T
}
class MyClass {
// 呼び出すとき使わないからオブジェクト名は省略可。インターフェースを実装したりもできる。
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
// staticメンバっぽく利用出来るけど、れっきとしたオブジェクト。
// オブジェクトそのものを取得したい場合はCompanionを指定する。
val x = MyClass.Companion
コンパイル時定数
頭に const 付けるとコンパイル時定数になる。次を満たしている必要がある。
- トップレベルか、object のメンバー
- 基本型で初期化される
- カスタムgetterがない
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
// アノテーション内でも使える
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
拡張
メソッドやプロパティのように使える関数を、既存クラスやオブジェクトに追加することができる。
// MutableList<Int>にswapを付け足す
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
val list = mutableListOf(1, 2, 3)
// こんな感じで使える
list.swap(0, 2) // swapメソッドの中のthisはlistを指す
// ジェネリクス関数としても追加できる
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
// Nullableも拡張できる
fun Any?.toString(): String {
// thisはnullの可能性がある!!
if (this == null) return "null"
return toString() // これはthis.toString()と同じだけど、thisは既にAny?でなくAny
}
// List<T>にlastIndexプロパティを付け足す
// バッキングフィールドにはアクセスできない。関数を拡張するのと同様にgetterを拡張している。
val <T> List<T>.lastIndex: Int
get() = size - 1
メソッドやプロパティのように呼び出せるだけで、実際にメソッドとして追加されるわけではない。__拡張の呼び出しは静的に解決される__ので、ポリモーフィズムには使えない。同じシグネチャのメソッドが既にある場合は、メンバが優先。
open class C
class D: C() // Cを継承
fun C.foo() = "c" // 拡張でCにfooメソッドを付け足す
fun D.foo() = "d" // 拡張でDにfooメソッドを付け足す
fun printFoo(c: C) {
println(c.foo()) // 拡張なのでC.foo()を呼び出すように静的に解決される。
}
// Cの子供のDを渡す。Dに拡張で足したD.foo()が呼び出され・・・ません!!
printFoo(D()) // "c"が表示される
コンパニオンオブジェクトも拡張できる。
class MyClass {
companion object { }
}
// MyClassのコンパニオンオブジェクトにfoo()を付け足す
fun MyClass.Companion.foo() {
// ...
}
// コンパニオンオブジェクトの他のメンバと同じように呼び出せる。
MyClass.foo()
大抵はトップレベルで定義する。使うには import する。
package foo.bar
fun Baz.goo() { ... }
package com.example.usage
import foo.bar.goo // "goo"って名前の全ての拡張をインポートする
// または
import foo.bar.* // "foo.bar"の全てをインポートする
// Baz.gooだけを狙い撃ちでインポートってのはできないっぽい
fun usage(baz: Baz) {
baz.goo()
)
接中辞(infix)記法
次の場合に接中辞記法を使うことができる。
- メンバ関数か拡張関数
- 引数が1つだけ
- 関数に infix キーワードが付けられている
//infixを付けてIntに拡張関数を追加
infix fun Int.shl(x: Int): Int {
...
}
//接中辞記法を使って拡張を呼び出す
1 shl 2
// 上は下と同じ
1.shl(2)
ラムダと無名関数
Kotlinでは関数は第1級オブジェクト(無名のリテラルとして表現でき、変数に代入したり、関数の引数に渡したり戻り値として返したりできる)。
// 高階関数(関数を引数に取る関数)
// body は () -> T の関数型で、引数なしで T 型の戻り値を返す関数を表している。
fun <T> lock(lock: Lock, body: () -> T): T {
// ...
}
// toBeSynchronized()関数を宣言
fun toBeSynchronized() = sharedResource.operation()
// このtoBeSynchronized()関数を渡したいのだが、それには::で参照を取得する
val result = lock(lock, ::toBeSynchronized)
// ラムダを渡す
val result = lock(lock, { sharedResource.operation() })
// 最後の引数が関数なら、引数を並べる括弧の外に出すのがKotlinの慣習
lock(lock) {
sharedResource.operation()
}
// 無名関数を渡す。無名関数は引数の括弧の外には出せない。
lock(lock, fun() { sharedResource.operation() })
引数を1つ取る関数を渡す場合は、ラムダの引数はこんな風に書ける。
// List内の各要素を、渡された関数を使って別のものに置き換えたListを返す
// transform は (T) -> R (T型1つを引数に取りR型を返す)型の関数。
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
// intsの各要素をitとして受け取り、それを2倍にした要素に置き換える
val doubled = ints.map { it -> it * 2 }
// ラムダに渡される引数が1つだけなら、その名前はデフォルトで it なので省略できる。
val m = ints.map { it * 2 }
引数を2つ取る関数を渡す場合はこんな感じ。
// less は (T, T) -> Boolean (2つのT型を引数に取りBooleanを返す)型の関数。
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it)) // ここでlessを呼び出している
max = it
return max
}
// ラムダを渡す。2つの引数をa, bとして取り、-> 以降の実行結果を返す。
val m = max(strings) { a, b -> a.length() < b.length() }
// a, bの型は推論可能なので省略している。ちゃんと書けば a: String, b: String -> ...
// 無名関数を渡す
val n = max(strings, fun(a, b) = a.length() < b.length())
無名関数は名前がないことと引数の型が(推論可能なら)省略できること以外は、普通の関数と書式は一緒。ラムダとの違いは戻り値の型を指定できることと、ブロック内で return するときの動作。ラベルを付けずに return した場合、ラムダは外側の関数を抜けるが、無名関数はその無名関数自身を抜ける。
どちらも外部環境をキャプチャしたクロージャを作れる。
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it // このラムダ式はブロックの外側にあるsumにアクセスして、しかも変更している
}
print(sum)
レシーバ付き関数リテラル
関数の引数には、普通の関数だけでなく、オブジェクトのメソッドのように呼び出される関数を渡すこともできる。ドットの前に書くオブジェクトのことをレシーバと呼ぶ3。
class HTML {
fun body() { ... }
}
// init引数はレシーバがHTML型で、引数なし、戻り値なし(Unit)の「レシーバ付き関数」
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // レシーバオブジェクトを作る
html.init() // htmlをレシーバとしてinitを呼び出す
return html
}
// html関数にレシーバ付きラムダを渡す
html { // レシーバ付きラムダ開始
body() // レシーバオブジェクトのメソッドを呼び出す。this.body()
}
上記のようなビルダーを作るのに便利。詳細は「Kotlin文法 - アノテーション、リフレクション、型安全なビルダー、動的型」の型安全なビルダーを参照。
クラス委譲
Kotlinのクラス委譲の機能を使うと、所持しているオブジェクトに単に委譲するだけのメソッドを作る面倒から解放される。
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// by を使ってBaseインターフェースの全てのpublicメソッドをbに委譲するよう指定
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print() // Derivedはbに委譲するので 10 を表示する
}
プロパティ委譲
class Example {
// getter/setterの動作をbyの後に指定したオブジェクトに委譲する
var p: String by Delegate()
}
委譲先のオブジェクトは適切な getValue(), setValue() メソッドを持っている必要がある。詳細は「Kotlin文法 - ネストされたクラス、Enumクラス、オブジェクト、委譲、プロパティ委譲」のプロパティ委譲を参照。
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}
このプロパティ委譲を使うことで、ありがちなプロパティ動作を共通化できる。例えば標準で lazy 関数が用意されており、初めてgetterが呼ばれたときに渡したブロックを実行するオブジェクトを生成する。
val lazyValue: String by lazy {
// 初めてlazyValueを取得するときにこのブロックが実行される
println("computed!")
"Hello" // これを結果として覚えておき、以後はずっとこれを返す
}
fun main(args: Array<String>) {
// 最初のアクセスなので"computed!"が表示された後、"Hello"が表示される
println(lazyValue)
// その後は"Hello"しか表示されない
println(lazyValue)
}
マップに格納した値にアクセスするプロパティ委譲も標準で提供されている。
import kotlin.properties.getValue // マップにgetValue()メソッドを拡張する
class User(val map: Map<String, Any?>) {
val name: String by map // mapに委譲する
val age: Int by map // mapに委譲する
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println(user.name) // "John Doe"と表示される
println(user.age) // "25"と表示される
型チェックとスマートキャスト
// 型チェックに is と !is が使える。
if (obj is String) { // obj がString(またはその派生)型かどうかチェック
// xがStringかどうかチェックしたので自動的にStringにキャストされている
print(obj.length) // Stringのプロパティlengthを呼び出せる
}
if (obj !is String) { // !(obj is String) と同じ
print("Not a String")
}
// `||`の右側ではxは自動的にStringにキャストされている
if (x !is String || x.length == 0) return
//`&&`の右側では x は自動的にStringにキャストされている
if (x is String && x.length > 0)
print(x.length) // xは自動的にStringにキャストされている
スマートキャストはNullableと同様に、チェックと利用の間で変数が変更されない場合にのみ有効。
明示的なキャスト
// これはyをStringにキャストするが、キャストできなかったら例外を投げる。
val x: String = y as String
// 上の場合、nullでも例外。nullを許容するにはNullableでないといけない
val a: String? = b as String?
// as? を使うと失敗時に例外を投げずnullを返す。なのでキャスト結果はNullableになる。
val x: String? = y as? String
例外
Kotlinの例外は全て Throwable の子孫。メッセージとスタックトレース、オプションで原因を持つ。
// 例外を投げるにはthrowを使う
throw MyException("Hi There!")
// throwは式として使える(評価結果はないが)。
// なのでエルビス演算子や値を返すwhenやifなどでも利用できる。
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// 例外をキャッチするにはtry-catche-finallyが使える。
try {
// なんかのコード
} catch (e: SomeException) {
// 例外処理
} finally {
// オプションでfinallyブロック。例外が起こっても起こらなくても実行される。
}
// Kotlinではtryは式であり値を返せる。
// tryかcatchの最後の式の評価結果が値になる。finallyは結果に関係ない。
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
Kotlinには検査例外(checked exception)はない。例外のキャッチは強制されない。
演算子オーバーロード
Kotlinでは演算子呼び出しは対応するメソッド呼び出しに変換される。なので対応するメソッドを用意することで、演算子をオーバーロードすることができる。
各演算子の変換ルール詳細は「Kotlin文法 - this、等価性、演算子オーバーロード、Null安全、例外」の演算子オーバーロードを参照。
単項演算子
式 | 変換先 |
---|---|
+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.mod(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.modAssign(b) |
ジェネリクス
クラスや関数は型パラメータを持てる。
class Box<T>(t: T) {
var value = t
}
// 一般書式では型引数を与えて使う
val box: Box<Int> = Box<Int>(1)
// でも型推論できる場合は省略できる
val box2 = Box(1) // 1の型はIntなのでコンパイラ にはBox<Int>だと分かる
// 関数も型パラメータを持てる。
fun <T> singletonList(item: T): List<T> {
// ...
}
// 関数の利用時に型パラメータを明示する場合は関数名の後ろに書く。もちろん推論できるなら省略可。
val l = singletonList<Int>(1)
// 拡張関数も
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
// ...
}
// 制約
// T は Comparable<T> のサブ型でないといけない。
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}
// 制約が複数
// TはComparableかつCloneableであることをwhereを使って制限
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
where T : Comparable, T : Cloneable {
return list.filter { it > threshold }.map { it.clone() }
}
宣言時に変性を指定できる。変性(variant)についての詳細は「Kotlin文法 - データクラス, ジェネリクス」のジェネリクスを参照。
// <out T>と書いてるから、Tは戻り値の型としてしか使わないよ。なのでTに対して共変として扱える。
abstract class Source<out T> {
abstract fun nextT(): T // 戻り値にTを使ってる
// 引数としてTを使ってるメソッドはない
}
fun demo(strs: Source<String>) {
// Source<String>をSource<Any>のサブ型として扱える。
val objects: Source<Any> = strs // これはOKだよ。だってTはoutパラメータだから。
// ...
}
// <in T>と書いているから、Tは引数の型としてしか使わないよ。なのでTに対して反変として扱える。
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int // 引数としてTを使ってる
// 戻り値としてTを使ってるメソッドはない
}
fun demo(x: Comparable<Number>) {
// DoubleはNumberのサブ型。Comparable<Double>にComparable<Number>を代入できる。
val y: Comparable<Double> = x // OK!
}
in, outに絞れない型を使う場合は、利用時に変性を指定できる。
// 引数fromの型パラメータにoutを指定してるから、
// fromは戻り値に型パラメータを使うメソッドしか使えないよ。
fun copy(from: Array<out Any>, to: Array<Any>) {
// ...
}
// 引数destの型パラメータにinを指定してるから、
// destは引数に型パラメータを使うメソッドしか使えないよ。
fun fill(dest: Array<in String>, value: String) {
// ...
}
型パラメータはなんでもいいってときには * を使う。
val ints: Array<Int> = arrayOf(1, 2, 3)
// 型パラメータが何かわかんなくても代入できる変数を用意。
val nums: List<*> = ints
// numsはリストとして何の型を内部に格納しているか不明なので、Any?としてしか取り出せない
val a: Any? = nums.get(0)
修飾されたthis
ラベルを付けない this は最も内側のものを指す。それ以外を指すにはラベルを付ける。
class A { // 暗黙的に@Aラベル
inner class B { // 暗黙的に@Bラベル
fun Int.foo() { // 暗黙的に@fooラベル
val a = this@A // Aのthis
val b = this@B // Bのthis
val c = this // foo()のレシーバ(Intオブジェクト)
val c1 = this@foo // foo()のレシーバ(Intオブジェクト)
val funLit = @lambda {String.() ->
val d = this // funLitのレシーバ
val d1 = this@lambda // funLitのレシーバ
}
val funLit2 = { (s: String) ->
val d1 = this // foo()のレシーバ。ラムダ式の中にはレシーバがないから。
}
}
}
}
Javaライブラリを利用する
KotlinからJavaを使うのは簡単。より詳細について、また逆にJavaからKotlinを呼び出す方法については「Kotlin - Javaとの相互運用」を参照。
getterとsetter
Javaのgetterやsetterは、標準的な名前が付けられていればKotlinからはプロパティとして扱える。ただし今のところKotlinではsetterしかないプロパティはサポートされていないため、setterしか提供されていない場合はプロパティとしては扱えない。
import java.util.Calendar
fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // getFirstDayOfWeek()を呼ぶ
calendar.firstDayOfWeek = Calendar.MONDAY // setFirstDayOfWeek()を呼ぶ
}
}
Kotlinでキーワードになっている識別子のエスケープ
JavaのメソッドがKotlinでキーワードになっている場合、バッククォートでエスケープして呼び出すことができる。
foo.`is`(bar)
Null安全
Javaでは全ての参照はnullになりえる。Javaの型は プラットフォーム型 と呼ばれ、Nullチェックは行われず、安全性はJavaと同じになる。
val list = ArrayList<String>() // nullでない (コンストラクタの結果)
list.add("Item")
val size = list.size() // nullでない (プリミティブ型のint)
val item = list[0] // プラットフォーム型と推論される(普通のJavaオブジェクト)
item.substring(1) // 許可される。item == null で例外が投げられるかも。
推論に任せずに型を明示することができる。
// 明示的にNullableに入れておくと安全
val item: String? = list[0]
// Nullalbeじゃない型でも許可されるけど、代入前にnullチェックのアサーションが自動挿入される。
val notNull: String = list[0] // 許可されるけど、実行時に失敗するかもしれない
プラットフォーム型をNullableでないKotlinの変数や引数に渡すと、コンパイラが自動的にアサーションを挿入する。実行時に即時失敗するので、KotlinのNullbaleでない型がnullを伝搬することはない。
SAM(Single Abstract Method)変換
Javaではメソッドが1つだけのインターフェースを受け取るケースがよくある。
// Java
executor.execute(new Runnable {
@Override
void run() {
println("This runs in a thread pool")
}
});
Kotlinでは以下のように書ける。コンパイラがラムダの内容でメソッドをオーバーライドした無名クラスのインスタンスを生成してくれる。
executor.execute { println("This runs in a thread pool") }
もしSAM変換可能な複数のインターフェースを取るようにオーバーロードされていたら、以下のようにどのインターフェースかを指定する。
executor.execute(Runnable { println("This runs in a thread pool") })
この機能はJavaとの相互運用でのみ利用できる。Kotlinでは関数を受け取るようにすればいいので。
その他
インライン関数
高階関数にラムダや無名関数を渡すと、それらはオブジェクト化される。これにはメモリ確保(こっちの方がコストがずっと大きい)と、呼び出し時にメモリからアドレスを取得するオーバーヘッドがかかる。
Kotlinには関数自体をインライン展開させたり、引数のラムダをインライン展開させて最適化する方法が用意されている。
また渡されるラムダをインライン展開させることで、ラムダ内でreturnが使えるようになる。
詳細は「Kotlin文法 - 関数とラムダ」のインライン関数を参照。
アノテーション
Javaと同様にアノテーションを使えるし、定義することもできる。「Kotlin文法 - アノテーション、リフレクション、型安全なビルダー、動的型」のアノテーションを参照。
リフレクション
Javaと同様にリフレクションを使える。「Kotlin文法 - アノテーション、リフレクション、型安全なビルダー、動的型」のリフレクションを参照。
末尾再帰
関数型プログラミングをサポートするために、末尾再帰を使って再帰をループに最適化することができる。詳細は「Kotlin文法 - 関数とラムダ」の末尾再帰を参照。
// 頭にtailrecを付けて、最後の処理を自身の呼び出しすると、ループに最適化される。
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))