皆さんこんにちは。ドワンゴ新卒1年目のdogwood008と申します。業務でAndroidアプリの作成に携わることがあり、その際にKotlinに初めて触れ、その可能性に惹かれてKotlinを好きになりました。
そこで、公式リファレンスを日本語化してみました。Kotlinがどんなものなのか、参考になれば幸いです(ちょっと長いので分けて投稿します(Kotlinの公式リファレンスを日本語化してみた[後編])。また翻訳に着手中のページもあるので、まだ英語のページもあります)。
基本的には上記ページの内容と、本ページの内容は同じです。個別のページで見たい人用と、一覧で見たい人用です。
他にも、以下のページ等でKotlinが紹介されています。参考にして下さい。
※2016/12/14追記
文体をですます調に変更し、全ての翻訳を見直しました。
基本的な構文
パッケージの定義
パッケージの記述は、ソースファイルの先頭に置かなければなりません。
package my.demo
import java.util.*
// ...
ディレクトリとパッケージと一致する必要はありません。ソースファイルは、ファイルシステム内の任意の場所に配置することができます。
パッケージを参照してください。
関数の定義
2つのInt
型の引数を持ち、Int
型を戻り値とする関数:
fun sum(a: Int, b: Int): Int {
return a + b
}
式本体と推論された戻り値の型を持つ関数:
fun sum(a: Int, b: Int) = a + b
意味のある値を返さない関数:
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
Unit
型の戻り値は省略できます:
fun printSum(a: Int, b: Int) {
print(a + b)
}
関数を参照してください。
ローカル変数の定義
1度だけ代入できる(読み取り専用)ローカル変数:
val a: Int = 1
val b = 1 // `Int`型が推論される
val c: Int // 初期値が与えられない場合、型指定が必要
c = 1 // 明確な代入
変更可能 (Mutable) な変数:
var x = 5 // `Int`型が推論される
x += 1
プロパティとフィールドも参照してください。
コメント
JavaとJavaScriptのように、Kotlinは行末コメントとブロックコメントをサポートしています。
// これは行末コメントです
/* これは複数行にわたる
ブロックコメントです。 */
Javaとは異なり、Kotlinのブロックコメントは入れ子にすることができます。
ドキュメンテーションコメントの構文の詳細については、Kotlinコードの文章化を参照してください。
文字列テンプレートの使用
fun main(args: Array<String>) {
if (args.size == 0) return
print("First argument: ${args[0]}")
}
文字列テンプレートを参照してください。
条件式の使用
fun max(a: Int, b: Int): Int {
if (a > b)
return a
else
return b
}
if{: .keyword }を式として使用:
fun max(a: Int, b: Int) = if (a > b) a else b
if{: .keyword }式を参照してください。
NULL可能値を使用した、null{: .keyword }のチェック
null{: .keyword }値を取り得る場合、参照は明示的にnullとしてマークする必要があります。
str
は整数を保持していない場合はnull{: .keyword }を返します:
fun parseInt(str: String): Int? {
// ...
}
null可能値を返す関数を使用:
fun main(args: Array<String>) {
if (args.size < 2) {
print("Two integers expected")
return
}
val x = parseInt(args[0])
val y = parseInt(args[1])
// `x`, `y`はnullが入っていることがあるので、`x * y`はエラーを引き起こす
if (x != null && y != null) {
// xとyは、nullチェックの後自動的に非null許容型へキャストされる
print(x * y)
}
}
または
// ...
if (x == null) {
print("Wrong number format in '${args[0]}'")
return
}
if (y == null) {
print("Wrong number format in '${args[1]}'")
return
}
// x and y are automatically cast to non-nullable after null check
print(x * y)
null安全を参照してください。
型チェックと自動キャストの使用
is{: .keyword }演算子はある式がある型のインスタンスであるかのチェックを行います。
不変のローカル変数やプロパティが特定の型であるかチェックされている場合は、明示的にキャストする必要はありません:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` はこのブランチ内では自動的に`String`へキャストされる
return obj.length
}
// `obj` は型チェックが行われたブランチ外では、まだ`Any`型である
return null
}
または
fun getStringLength(obj: Any): Int? {
if (obj !is String)
return null
// `obj` はこのブランチ内では自動的に`String`へキャストされる
return obj.length
}
あるいは
fun getStringLength(obj: Any): Int? {
// `obj` は`&&`の右側では自動的に`String`へキャストされる
if (obj is String && obj.length > 0)
return obj.length
return null
}
for
ループの使用
fun main(args: Array<String>) {
for (arg in args)
print(arg)
}
または
for (i in args.indices)
print(args[i])
forループを参照してください。
while
ループの使用
fun main(args: Array<String>) {
var i = 0
while (i < args.size)
print(args[i++])
}
whileループを参照してください。
when
式の使用
fun cases(obj: Any) {
when (obj) {
1 -> print("One")
"Hello" -> print("Greeting")
is Long -> print("Long")
!is String -> print("Not a string")
else -> print("Unknown")
}
}
when式を参照してください。
範囲の使用
in{: .keyword }演算子を使用すると、数が範囲内にあるかどうかを確認できます:
if (x in 1..y-1)
print("OK")
数が範囲外であるかどうかを確認します:
if (x !in 0..array.lastIndex)
print("Out")
範囲内で反復処理:
for (x in 1..5)
print(x)
範囲を参照してください。
コレクションの使用
コレクション内で反復処理:
for (name in names)
println(name)
コレクションがあるオブジェクトを含むかをin{: .keyword }演算子で調べる:
if (text in names) // names.contains(text) が呼ばれる
print("Yes")
フィルタやマップコレクションにラムダ式を使用します:
names
.filter { it.startsWith("A") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { print(it) }
高階関数とラムダを参照してください。
イディオム
Kotlinでよく使用されるイディオムを集めました。もし好みのイディオムがあれば、プルリクエストを投げて貢献してください。
DTOの作成(POJO/ POCO)
data class Customer(val name: String, val email: String)
Customer
クラスは次の機能を提供します:
- 全てのプロパティのゲッター(とvar{: .keyword }キーワードが使用されたときはセッターも)
equals()
hashCode()
toString()
copy()
- すべてのプロパティに対して、
component1()
,component2()
, …, (Data classesを参照してください)
関数の引数に対するデフォルト値
関数の引数に対するデフォルト値
fun foo(a: Int = 0, b: String = "") { ... }
リストのフィルタリング
val positives = list.filter { x -> x > 0 }
または、こう短くも書けます:
val positives = list.filter { it > 0 }
文字列補完
println("Name $name")
インスタンスのチェック
when (x) {
is Foo -> ...
is Bar -> ...
else -> ...
}
mapやlistのペアを書き下す
for ((k, v) in map) {
println("$k -> $v")
}
k
, v
は何からでも呼び出すことができます。
範囲の使用
for (i in 1..100) { ... }
for (x in 2..10) { ... }
読み取り専用list
val list = listOf("a", "b", "c")
読み取り専用map
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
mapへのアクセス
println(map["key"])
map["key"] = value
遅延評価プロパティ
val p: String by lazy {
// 文字列を評価
}
拡張関数
fun String.spaceToCamelCase() { ... }
"Convert this to camelcase".spaceToCamelCase()
シングルトンの作成
object Resource {
val name = "Name"
}
if not nullの省略記法
val files = File("Test").listFiles()
println(files?.size)
if not null and elseの省略記法
val files = File("Test").listFiles()
println(files?.size ?: "empty")
if null文の実行
val data = ...
val email = data["email"] ?: throw IllegalStateException("Email is missing!")
if not null文の実行
val data = ...
data?.let {
... // nullでなければこのブロックを実行する
}
when文でreturnする
fun transform(color: String): Int {
return when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
}
'try / catch'式
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
// resultを使って何かする
}
'if'式
fun foo(param: Int) {
val result = if (param == 1) {
"one"
} else if (param == 2) {
"two"
} else {
"three"
}
}
Unitを返すメソッドのビルダースタイルの使用
fun arrayOfMinusOnes(size: Int): IntArray {
return IntArray(size).apply { fill(-1) }
}
単一式関数
fun theAnswer() = 42
これは次と等価です:
fun theAnswer(): Int {
return 42
}
これは他のイディオムと組み合わせることができ、コードを短くすることにつながります。例) when{: .keyword }-式:
fun transform(color: String): Int = when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
'with' を使って、あるオブジェクトのインスタンスで複数の関数を呼ぶ
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) { // 100pxの四角形を描く
penDown()
for(i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}
Java 7のtry-with-resources
val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use { reader ->
println(reader.readText())
}
コーディング規約
このページでは、Kotlin言語の現在のコーディングスタイルを紹介します。
命名スタイル
よく分からない場合はJava のコード規約に従ってください:
- キャメルケースの使用(そして命名でのアンダースコアの使用を避ける)
- 型は大文字で始まる
- メソッドとプロパティは小文字で始まる
- 4スペースインデントの使用
- public関数はKotlin Docに登場するようなドキュメンテーションがないといけない
コロン
コロンが型と継承元のセパレータとして使用される際は、一つ前にスペースが必要です。 一方、インスタンスと型のセパレータとして使用するときには、スペースは不要です。
interface Foo<out T : Any> : Bar {
fun foo(a: Int): T
}
ラムダ
ラムダ式では、スペースが波括弧の周りに必要です。また、パラメータを本体と分かつアロー(->)も同様です。 可能な限り、ラムダを括弧の外に渡す必要があります。
list.filter { it > 10 }.map { element -> element * 2 }
短くてネストされていない(入れ子でない)ラムダ内では、 パラメータを明示的に宣言する代わりに it
規約を使用することを推奨します。パラメータを持つネストされたラムダでは、パラメータを常に明示的に宣言する必要があります。
ユニット (Unit)
関数がUnitを返す場合、戻り値の型は省略されるべきです:
fun foo() { // ": Unit" が省略されている
}
基本の型
Kotlinでは、メンバ関数やプロパティをどんな変数からでも呼び出せるという意味で、全てのものがオブジェクトです。 いくつかの型は実装が最適化されているためビルトインであるが、ユーザから見ると普通のクラスのように見えます。このセクションでは、次の型の大部分を説明します:数値、文字、真偽値(boolean)、配列。
数値
Kotlinは、Javaに近い方法で数字を扱うが、全く同じではありません。例えば、数値の暗黙の拡大変換が存在せず、リテラルはいくつかの事例では少し異なる。
Kotlinは数値の表現用に次のビルトインの型を提供します(これはJavaに近い):
型 | ビット幅 |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
Kotlinでは文字は数値でないことに注意してください。
リテラル定数
整数値のためのリテラル定数は、次の種類があります。
- 数値:
123
- Long型の数を表すには大文字の
L
でタグ付けする:123L
- Long型の数を表すには大文字の
- 16進数:
0x0F
- 2進数:
0b00001011
注:8進数のリテラルはサポートされていません。
Kotlinは浮動小数点数の従来の表記法もサポートしています。
- デフォルトではdouble型:
123.5
,123.5e10
- float型を表すには
f
orF
でタグ付けする:123.5f
表現
Javaプラットフォームでは、null許容型な数値の参照(例:Int?
)やジェネリクスが関与している場合を除いて、JVMプリミティブ型として数値が物理的に格納されています。後者の例では番号がボクシングされています。
数値のボクシングは一様性を保持しないことに注意してください:
val a: Int = 10000
print(a === a) // 'true'を出力する
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!! 'false'を出力する !!!
一方、これは同一性を保持しています:
val a: Int = 10000
print(a == a) // 'true'を出力する
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // 'true'を出力する
明示的な変換
異なる表現であるが故に、小さな型は大きな型のサブタイプではありません。 もし仮にそうであったならば、次の種類の悩みを抱えたでしょう:
// 仮説のコードであり、実際はコンパイルできません:
val a: Int? = 1 // ボクシングされたInt型 (java.lang.Integer)
val b: Long? = a // 暗黙の変換がLong型 (java.lang.Long)へのボクシングを引き起こします
print(a == b) // 仰天!これはLong型のequals()チェックで他の部分がLong型になるのと同等に "false" を出力します
つまり、同一性だけでなく一様性でさえも全ての場所において静かに失われたのです。
その結果、小さな型は、暗黙的に大きな型に変換されるのではありません。これは明示的変換無しでByte
型の値をInt
型へ代入することができないことを意味します。
val b: Byte = 1 // OK, リテラルは静的にチェックされています
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
明示的変換がないことは滅多に目立ちません。なぜならその型は文脈から推測され、算術演算がオーバロードされ適切に変換されるからです。例えば:
val l = 1L + 3 // Long + Int => Long
演算
Kotlinは算術計算の標準セットをサポートしています。それらは適切なクラス(ただしコンパイラは対応する命令の呼び出しを最適化する)のメンバとして宣言されています。演算子のオーバーロードを参照してください。
ビット演算にはそのような特殊な文字列がありませんが、中置形で呼び出せる名前付き関数があります。例えば:
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: 非互換の型
// ...
}
}
[translation here]
文字リテラルを表現するには、シングルクォートで囲みます: '1'
特殊文字はバックスラッシュを使ってエスケープすることができます。
次のエスケープシーケンスがサポートされています:\t
, \b
, \n
, \r
, \'
, \"
, \\
, \$
他の文字をエンコードするには、Unicodeエスケープシーケンスの構文を使用します:'\uFF00'
明示的に文字をInt
型の数に変換することもできます。
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // 暗黙的な数値への変換
}
数値のように、文字はnull許容参照が必要なときにボクシングされます。同一性ははボクシング操作されると保持されません 。
真偽値 (Boolean)
[translation here]
Boolean
型は真偽値を表し、true{: .keyword }とfalse{: .keyword }の2つの値があります。
Booleanはnull許容参照が必要なときにボクシングされます。
Booleanのビルトイン演算は次を含みます:
-
||
– 遅延評価論理和 -
&&
– 遅延評価論理積 -
!
- 否定
配列
Kotlinでの配列は Array クラスで表され、get
とset1関数を持ちます(
[]の演算子をオーバロードすることによって実現している)。また、
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>
// ...
}
配列を作るには、ライブラリ関数のarrayOf()
にアイテムの値を渡してください。つまり、arrayOf(1, 2, 3)
は[1, 2, 3]の配列を作成します。
あるいはライブラリ関数のarrayOfNulls()
で、null要素で埋められた指定サイズの配列を作ることができます。
他のやり方として、配列のサイズと配列のインデックス値を与えると各要素の初期値用に値を返す関数を引数にとるファクトリ関数の使用があります。
// Array<String>を["0", "1", "4", "9", "16"]の値で作成します
val asc = Array(5, { i -> (i * i).toString() })
前述したとおり、[]
演算はメンバ関数のget()
とset()
の呼び出しを表します。
注:Javaとは異なり、Kotlinの配列は不変です。つまりKotlinではArray<Any>
へArray<String>
を代入することができないということを表します。これは実行時エラーを回避するためです(しかし、Array<out Any>
を使えば代入できます。型プロジェクションを参照してください)。
Kotlinはプリミティブ型(ByteArray
、ShortArray
、IntArray
等)の配列について、オーバーヘッド無しでボクシングができる特別なクラスを持ちます。 これらのクラスはArray
クラスと継承関係を持ちませんが、同じメソッドとプロパティを持ちます。 それぞれのクラスにおいて、対応するファクトリ関数を持ちます:
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
文字列
文字列は、String
型で表されます。文字列は不変(イミュータブル)です。
文字列の要素は、インデックスの演算でアクセスできます:s[i]
文字列は for{: .keyword }ループでイテレート(繰り返し操作)できます:
for (c in str) {
println(c)
}
文字列リテラル
Kotlinは2つの種類の文字列リテラルを持ちます:1つはエスケープされた文字列を持ちうるエスケープ済み文字列で、もう1つは改行と任意の文字を含む生文字列です。 エスケープ済み文字列はJavaの文字列に非常によく似ています:
val s = "Hello, world!\n"
エスケープは、バックスラッシュを用いて従来の方法で行われます。サポートされているエスケープシーケンスのリストについては、文字列を参照してください。
生文字列は三連クオート ("""
) で区切られます。エスケープは含まれておらず、改行や他の文字を含めることができます:
val text = """
for (c in "foo")
print(c)
"""
先頭の空白をtrimMargin()
関数で削除することができます。
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
デフォルトでは|
はマージンの接頭辞として使用されますが、trimMargin(">")
のように、パラメータとして別の文字を渡すとそれを接頭辞として使用することができます。
文字列テンプレート
文字列はテンプレート式、すなわち、評価され、その結果が文字列と結合されるコードの断片を含むことができる。テンプレート式は、ドル記号($)で始まり、簡単な名前で構成されます:
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
"""
パッケージ
パッケージ宣言をするときは、ソースファイルの先頭に書いてください。
package foo.bar
fun baz() {}
class Goo {}
// ...
ソースファイルの(このようなクラスや関数など)全ての内容は宣言パッケージに含まれています。従って、次の例で示すとおり、baz()
の完全名はfoo.bar.baz
であり、Goo
の完全名はfoo.bar.Goo
です。
もしパッケージが指定されない場合は、ファイルの内容は名前を持たない”default”パッケージに属することになる。
インポート
標準のインポートとは違い、それぞれのファイルは独自のインポートディレクティブを含んでもかまいません。
インポートの文法は、文法に記載されています。
単一の名前を指定してインポートできます。例:
import foo.Bar // Barは許可無しでアクセス可能になります
または、あるスコープ(パッケージ、クラス、オブジェクト等)内の全てのアクセス可能なコンテンツの場合:
import foo.* // 'foo'内の全てがアクセス可能になります
名前の衝突がある場合、as{: .keyword }キーワードを使用して衝突するエンティティを局所的にリネームすることで明確にできます:
import foo.Bar // Barはアクセス可能
import bar.Bar as bBar // bBarは'bar.Bar'を意味する
import キーワードはクラスをインポートするために限定されるわけではありません。他の宣言をインポートするために使用することができます:
Javaとは違って、Kotlinは別の"import static"構文を持っていません。全ての宣言は普通のimport
キーワードによってインポートされます。
トップレベル宣言の可視性
もしトップレベルの宣言にprivate{: .keyword }マークがついていれば、それが宣言されたファイル内に対しプライベートです。 (可視性修飾子 を参照してください。)
制御フロー
if式
Kotlinでは、if{: .keyword }は式であり、すなわち値を返します。従って、三項演算子は存在しません(条件 ? 真の時 : 偽の時)。なぜなら普通の if{: .keyword } がその役割を果たすためです。
// 伝統的な使い方
var max = a
if (a < b)
max = b
// else付き
var max: Int
if (a > b)
max = a
else
max = b
// 表現として
val max = if (a > b) a else b
if{: .keyword } の分岐はブロックにすることができ、最後の式がそのブロックの値となります:
val max = if (a > b) {
print("Choose a")
a
}
else {
print("Choose b")
b
}
もしif{: .keyword }を文ではなく式として使用する(例えば値を返したり変数に代入したりする)ならば、その式にはelse
分岐が必要です。
if{: .keyword }の文法を参照してください。
when式
when{: .keyword } はC言語のような言語におけるswitch演算子の置き換えです。最も簡単な形式では、次のようになります:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // このブロックに注目してください
print("x is neither 1 nor 2")
}
}
when{: .keyword } はその引数と条件が満たされる分岐が現れるまで、順番に全ての分岐に対して比較されます。when{: .keyword } は式としても文としても使うことができます。 もし式として使用されれば、その値は条件が満たされた分岐が全ての式の値となります。もし文として使用されれば、個別の条件は無視されます。(if{: .keyword }と全く同じく、それぞれの条件はブロックになれるため、その値はブロック内の最後の式のものとなる。)
else{: .keyword }条件は他の条件が全て満たされなかった際に評価されます。 もしが式として使用されれば、全てのあり得る場合を分岐条件で網羅できていることをコンパイラが証明できない限りは、else{: .keyword }条件は必須です。
もしたくさんの条件を同じ方法で処理する必要がある場合には、分岐条件をコンマでまとめることができます:
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("それ以外")
}
分岐条件として任意の式(定数に限らない)を使用することができます:
when (x) {
parseInt(s) -> print("sはxをエンコードする")
else -> print("sはxをエンコードしない")
}
in{: .keyword }または*!in*{: .keyword }を使用すると、コレクションの 範囲 (range) をチェックすることもできます:
when (x) {
in 1..10 -> print("xは範囲内")
in validNumbers -> print("xは有効")
!in 10..20 -> print("xは範囲外")
else -> print("どれにも該当せず")
}
値をチェックする他の方法として、特定の型のis{: .keyword }または*!is*{: .keyword }があります。スマートキャストのおかげで、その型のメソッドやプロパティに追加のチェック無しでアクセスできることに注意してください。
val hasPrefix = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
when{: .keyword }は if{: .keyword}-else{: .keyword} if{: .keyword}連鎖を代替することもできます。 引数が与えられない場合は、分岐条件は単純なbooleanの式となり、分岐はその条件がtrueの場合に実行されます:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
when{: .keyword } の文法を参照してください。
Forループ
for{: .keyword }ループはイテレータによって提供されるものを何でも繰り返し実行します。構文は次のとおりです:
for (item in collection)
print(item)
本文をブロックにすることもできます。
for (item: Int in ints) {
// ...
}
前述したように、 for{: .keyword} はイテレータとして提供されるものを何でも繰り返し実行します。すなわち:
- メンバ関数や拡張関数の
iterator()
は型を返し、- メンバ関数や拡張関数の
next()
と - メンバ関数や拡張関数の
hasNext()
はBoolean
を返します。
- メンバ関数や拡張関数の
これら3つの関数は全て 演算子 (operator)
としてマークされる必要があります。
配列のfor
ループはイテレータオブジェクトを作成しないインデックスベースのループにコンパイルされます。
もし配列やリストをインデックス付きで繰り返し処理したいならば、この方法を使用できる:
for (i in array.indices)
print(array[i])
“範囲の繰り返し実行”は余分なオブジェクトを生成しない最適な実装へコンパイルされることに注意すること。
別方法として、ライブラリ関数の withIndex を使用することもできます:
for ((index, value) in array.withIndex()) {
println("$indexの要素は$value")
}
for{: .keyword }の文法を参照してください。
whileループ
while{: .keyword } と do{: .keyword }..while{: .keyword } はいつものように動きます:
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y はここで可視(visible)
while{: .keyword }の文法を参照してください。
ループ内でのbreakとcontinue
Kotlinはループ中の従来の break{: .keyword }とcontinue{: .keyword } 演算子をサポートしています。Returnとジャンプを参照してください。
returnとジャンプ
Kotlinには3つの構造的ジャンプ演算子があります。
- return{: .keyword }. デフォルトでは最近のクロージャ(関数閉包)や匿名関数から抜け出します。
- break{: .keyword } 最も近い外側のループを終わらせます。
- continue{: .keyword }. 最も近い外側のループである次のステップに進みます。
breakとcontinueのラベル
Kotlinにおける任意の式を label{: .keyword }でマークすることができます。ラベルは、たとえば、 @
記号に続く識別子の形式を持っています: abc@
、fooBar@
が有効なラベル(文法を参照してください)です。式にラベルを付けるには、式の前にラベルを置きましょう:
loop@ for (i in 1..100) {
// ...
}
さあ、これで私たちは break{: .keyword } や continue{: .keyword } をラベル付けできるようになりました:
loop@ for (i in 1..100) {
for (j in 1..100) {
if (...)
break@loop
}
}
ラベル付き break{: .keyword } はそのラベルが付いたループの右後の実行ポイントへジャンプします。
continue{: .keyword } はそのループの次の繰り返し実行(イテレーション)まで進みます。
ラベルに復帰する
Kotlinでは、関数リテラル、ローカル変数、オブジェクト式を使用すると、関数を入れ子にすることができます。
ある条件付きの return{: .keyword }を使うと、外側の関数から復帰することができます。
最も重要なユースケースは、ラムダ式からのreturnです。これを書くとき、思い出してください:
fun foo() {
ints.forEach {
if (it == 0) return
print(it)
}
}
return{: .keyword } 式は最も内側の関数、すなわち foo
から復帰します。
(このような非局所的復帰がインライン関数に渡されたラムダ式でのみサポートされていることに注意してください。)
もしラムダ式から復帰する必要がある場合は、それにラベルを付け、 return{: .keyword } を修飾する必要があります:
fun foo() {
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
}
これはラムダ式からのみ復帰します。多くの場合、暗黙のラベルを使用する方が便利です。
そのようなラベルは、ラムダが渡された関数と同じ名前を持っています。
fun foo() {
ints.forEach {
if (it == 0) return@forEach
print(it)
}
}
代わりの手法として、無名関数とラムダ式を置き換えることができます。
無名関数内の return{: .keyword } 文は、その無名関数自体から復帰します。
fun foo() {
ints.forEach(fun(value: Int) {
if (value == 0) return
print(value)
})
}
値を返すとき、パーサは資格を持つreturnを優先します。すなわち、
return@a 1
上記は「 @a
ラベルにおけるreturn 1
」を意味し、「 (@a 1)
ラベルが付いた式からのreturn」ではありません。
クラスと継承
クラス
Kotlinでのクラスは、class{: .keyword }キーワードを使用して宣言されます。
class Invoice {
}
クラス宣言はクラス名、クラスヘッダ(その型パラメータ、主コンストラクタ等)、そして波括弧で括られたクラス本体で構成されます。ヘッダと本体は両方とも必須ではありません。クラスに本体がない場合は、波括弧を省略することができます。
class Empty
コンストラクタ
Kotlin内のクラスは、 プライマリコンストラクタ と1つまたは複数の セカンダリコンストラクタ を持つことができます。プライマリコンストラクタは、クラスのヘッダーの一部です。クラス名(型パラメータをつけることもできます)の後に続きます。
class Person constructor(firstName: String) {
}
プライマリコンストラクタがアノテーションや可視性修飾子を持っていない場合は、 constructor{: .keyword }のキーワードを省略することができます。
class Person(firstName: String) {
}
プライマリコンストラクタは、どんなコードも含めることはできません。初期化コードは、init{: .keyword }キーワードが付いている 初期化ブロック内 に書くことができます。
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
プライマリコンストラクタの引数を初期化ブロックに使用できることに注意してください。クラス本体内で宣言されたプロパティの初期化処理で使用することもできます。
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
実際には、プロパティの宣言と初期化を主コンストラクタから行うために、Kotlinは簡潔な構文があります:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
通常のプロパティとほとんど同じ方法のように、プロパティは主コンストラクタの中で可変値(ミュータブル) ( var{: .keyword } ) または固定値(イミュータブル) ( val{: .keyword} ) で宣言することができます。
もしコンストラクタがアノテーションや可視性修飾子を持つ場合は、 constructor{: .keyword } キーワードが必要で修飾子はその前に置かれる:
class Customer public @Inject constructor(name: String) { ... }
詳細については、可視性修飾子を参照してください。
セカンダリコンストラクタ
クラスは、 constructor{: .keyword } プレフィクスと共に セカンダリコンストラクタ を宣言することができます:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
もしクラスがプライマリコンストラクタを持つなら、それぞれのセカンダリコンストラクタは直接的または間接的に、他のセカンダリコンストラクタを介してプライマリコンストラクタへ委譲する必要があります。 同クラスの他コンストラクタへの委譲は this{: .keyword } キーワードを用いて行います:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
もし非抽象クラスが何もコンストラクタ(プライマリ、セカンダリ共に)を宣言しなければ、プライマリコンストラクタが引数無しで生成されます。その際のコンストラクタの可視性はpublicになります。もしpublicなコンストラクタを望まないならば、空の主コンストラクタをデフォルトでない可視性で宣言する必要があります。
class DontCreateMe private constructor () {
}
注意: JVMでは、プライマリコンストラクタの全ての引数がデフォルト値を持つなら、
コンパイラは引数無しコンストラクタを追加で生成し、そのコンストラクタはデフォルト値を使用します。
これにより、JacksonやJPAのように引数が無いコンストラクタを通してクラスインスタンスを作るようなライブラリを、
Kotlinで使いやすくなります。class Customer(val customerName: String = "")
{:.info}
クラスのインスタンス生成
クラスのインスタンスを生成するには、コンストラクタを普通の関数のように呼び出せば良いです:
val invoice = Invoice()
val customer = Customer("Joe Smith")
Kotlinは new{: .keyword } キーワードを持たないことに注意してください。
ネストされたクラス、インナークラス、そして匿名のインナークラスの生成はネストされたクラスの中に記述されています。
クラスメンバ
クラスは以下を含めることができます:
- コンストラクタと初期化ブロック
- 関数
- プロパティ
- ネストされたインナークラス
- オブジェクトの宣言
継承
Kotlinの全てのクラスは共通の Any
スーパークラスをもちます。これはスーパータイプの宣言がないクラスのデフォルトのスーパークラスです。
class Example // Anyから暗黙の継承
Any
は java.lang.Object
ではありません。特に注意すべきは、 equals()
、 hashCode()
、 toString()
以外のメンバを持ちません。 詳細については Javaとの相互運用性 を参照してください。
クラスヘッダ内のコロンの後に型を書くと、明示的にスーパータイプを宣言できます:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
もしこのような(明示的にスーパータイプを宣言する)クラスがプライマリコンストラクタをもつなら、基底の型をプライマリコンストラクタの引数を使用して、そこで初期化できる(し、しなければいけません)。
もしこのようなクラスがプライマリコンストラクタを持たないならば、セカンダリコンストラクタはそれぞれ基底の型を super{: .keyword } キーワードを使って初期化するか、他の初期化してくれるコンストラクタに委譲しなければいけません。この事例では異なるセカンダリコンストラクタが異なる基底の型を持つコンストラクタを呼び出していることに注意すること:
class MyView : View {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
}
}
クラスの open{: .keyword } アノテーションは、Javaの final{: .keyword } と反対です:他のクラスがこのクラスから継承することができます。デフォルトでは、Kotlinのすべてのクラスは Effective Java のアイテム17( 継承またはそれの禁止のためのデザインとドキュメント )に合致する final です。
メンバのオーバーライド
前述の通り、私たちはKotlinに明示的にすることにこだわります。そして、Javaとは異なり、Kotlinはメンバをオーバーライドできるメンバ(私たちは open と呼んでいます)とオーバライド自体に明示的アノテーションを必要とします。
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
override{: .keyword } アノテーションは Derived.v()
のために必要です。もしなければ、コンパイラは文句を言うでしょう。もし Base.nv()
のように open{: .keyword } アノテーションが関数になければ、メソッドをサブクラス内で同じ識別子で宣言することは override{: .keyword } の有無にかかわらず文法違反です。ファイナルクラス(例えば、 open{: .keyword } アノテーションを持たないクラス)の中では、openメンバは禁止されています。
override{: .keyword } としてマークされているメンバは、それ自身がopenです。すなわち、サブクラス内でオーバライドされる可能性があります。もし再オーバライドを禁止したければ、 final{: .keyword } キーワードを使ってください:
open class AnotherDerived() : Base() {
final override fun v() {}
}
プロパティのオーバライドもメソッドのオーバライドと同じように動きます。プライマリコンストラクターでプロパティ宣言の一部として、overrideキーワードを使用できることに注意してください。
open class Foo {
open val x: Int get { ... }
}
class Bar1(override val x: Int) : Foo() {
}
val
プロパティを var
プロパティでオーバライドすることもでき、その逆もまた然りです(逆もまた同じです)。これは、val
のプロパティは、本質的にgetterメソッドを宣言しているためであり、それを var
としてオーバライドすることは、さらにsetterメソッドを派生クラスに宣言しているためです。
待って!じゃあどうやって自分のライブラリをハックすれば良いの?!
オーバライドのKotlinでの方法(クラスやメンバはデフォルトでfinal)には1つ問題があります。あなたが使用しているライブラリ内の何かをサブクラス化し、 いくつかのメソッドをオーバライドして(ライブラリの設計者はそれを意図していない) そこにいくつかの厄介なハックを導入するのが難しくなる、という問題です。
私たちは、次のような理由から、これは欠点ではないと考えています:
- ベストプラクティスは「とにかくこれらのハックを許可すべきではない」ということである
- 同様のアプローチを取っている他の言語 (C++, C#) はうまくいっている
- もし本当にこのハックが必要ならば、それでも方法は残っている:いつでもハックをJavaで書き、Kotlinから呼ぶことができる( Java Interopを参照 してください )し、Aspectフレームワークはいつもこれらの目的にかないます
ルールのオーバーライド
Kotlinでは、継承の実装は次のルールで定められています:もしクラスが直接のスーパークラスから同じメンバの多くの実装を継承する場合、クラスはこのメンバを継承し、その独自の実装(おそらく、継承されたものの一つを使用して)を提供しなければいけません。スーパータイプの名前を角括弧で記述し、 super キーワードを使用すると、継承された実装のスーパータイプであることを示すことができます。 例: super<Base>
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // インタフェースのメンバはデフォルトで'open'
fun b() { print("b") }
}
class C() : A(), B {
// オーバライドするためにコンパイラは f() を要求する
override fun f() {
super<A>.f() // A.f()の呼び出し
super<B>.f() // B.f()の呼び出し
}
}
A
と B
の両方から継承するのは問題なく、 C
はそれらの関数の唯一の実装であるため a()
と b()
も同様です。しかし f()
については、2つの実装が C
に継承されているため、 C
にある f()
をオーバライドし、曖昧さを排除するため独自の実装を提供する必要があります。
抽象クラス
クラスとそのメンバは abstract{: .keyword } を使用して抽象クラス・抽象メンバとして宣言することができます。抽象メンバはそのクラス内に実装を持ちません。抽象クラスや抽象関数にopenアノテーションを付ける必要はないことに注意してください。もっとも、それは言うまでもないことですが。
非抽象オープンメンバを抽象メンバでオーバライドすることもできます。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
コンパニオンオブジェクト (Companion Objects)
Kotlinでは、JavaやC#とは異なり、クラスはstaticメソッドを持ちません。ほとんどの場合、代替として、パッケージレベルの関数を使用することが推奨されています。
もしクラスインスタンスを持たずに呼べるがクラス内部(例えばファクトリメソッド)へのアクセスが要る関数を書く必要があれば、そのクラスの中で オブジェクト宣言 のメンバとして書くことができます。
特に、もし コンパニオンオブジェクト をクラス内で宣言した場合であっても、クラス名を識別子として、static関数をJava/C# で呼ぶのと同じ構文でそのメンバを呼ぶことができます。
シールクラス (Sealed Classes)
値が制限されたセットの1つの型を持つが、他の型を持てない場合、シールクラスが制限されたクラス階層を表現する際に用いられます。それらはある意味、enum(列挙型)クラスの拡張です。enum型の値のセットも同じく制限されているが、それぞれのenum定数はシングルインスタンスとしてのみ存在し、シールクラスのサブクラスは状態を保持できる複数のインスタンスをもつことができます。
sealed
修飾子をクラス名の前に置くと、シールクラスを宣言できます。ルクラスはサブクラスを持つことができますが、それらは全てシールクラス自身の宣言の中にネストされていなければいけません。
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
シールクラスのサブクラス(間接的な継承)を拡張するクラスはどこにでも置くことができ、シールクラスの中に書く必要はないことに注意してください。
シールクラスの主な利点は when
式 の中で使用されたときに発揮されます。もし文が全ての事象をカバーすることを確認・証明できれば、 else
句を追加する必要はありません。
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` 句は不要
}
プロパティとフィールド
プロパティの宣言
Kotlinのクラスは、プロパティを持つことができます。
これらは、 var{: .keyword } キーワードを使用して、ミュータブル(可変)として宣言することもでき、 val{: .keyword } キーワードを使用するとイミュータブル(読み取り専用)にすることもできます。
public class Address {
public var name: String = ...
public var street: String = ...
public var city: String = ...
public var state: String? = ...
public var zip: String = ...
}
プロパティを使うにはJavaでのフィールドでやるように、ただ単純に名前で参照するだけで良いです:
fun copyAddress(address: Address): Address {
val result = Address() // 'new' キーワードは Kotlin にありません
result.name = address.name // アクセサが呼ばれる
result.street = address.street
// ...
return result
}
ゲッターとセッター
プロパティを宣言するための完全な構文は次のとおりです
var <propertyName>: <PropertyType> [= <property_initializer>]
[<getter>]
[<setter>]
イニシャライザ、ゲッターとセッターは必須ではありません。イニシャライザか基本クラスのメンバーからオーバライドされることが推測される場合は、プロパティの型も必須ではありません。
例:
var allByDefault: Int? // エラー:明示的なイニシャライザが必要、デフォルトのゲッターとセッターは暗黙
var initialized = 1 // これは Int 型を持ち、ゲッターとセッターも持つ
読み取り専用のプロパティ宣言の完全な構文は、ミュータブルのものと比べて2点異なります。var
の代わりに val
で始まるのと、セッターを認めないことでです:
val simple: Int? // Int 型を持ち、デフォルトゲッターを持つ。コンストラクタ内で初期化が必要
val inferredType = 1 // Int 型を持ち、デフォルトゲッターを持つ
カスタムアクセサは普通の関数ととても似ていて、プロパティの中に宣言することができます。ここでは、カスタムゲッターの例を示します:
val isEmpty: Boolean
get() = this.size == 0
カスタムセッターは次のようになります:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 文字列をパースして他のプロパティへ値を代入する
}
慣例により、セッターの引数名は value
ですが、別の名前が良いならそちらを選択することもできます。
アクセサの可視性を変更したり、アノテーションを付ける必要がありますが、デフォルトの実装を変更する必要がない場合は、その本体を定義せずにアクセサを定義することができます:
var setterVisibility: String = "abc"
private set // セッターはプライベートでデフォルトの実装を持つ
var setterWithAnnotation: Any? = null
@Inject set // セッターに Inject でアノテーションを付ける
バッキングフィールド (Backing Fields)
Kotlinのクラスは、フィールドを持つことができません。しかし、カスタムアクセサを使用するときにバッキングフィールドが必要になることがあります。この目的のために、Kotlinは自動バッキングフィールドを提供します。これにより、 field
識別子を使用してアクセスすることができます。
var counter = 0 // イニシャライザの value はバッキングフィールドへ直に書き込まれる
set(value) {
if (value >= 0)
field = value
}
field
識別子は、プロパティのアクセサにのみ使用することができます。
プロパティがアクセサのデフォルトの実装のうち少なくとも1つを使用するか、カスタムアクセサが field
識別子を通して参照された場合に、バッキングフィールドは生成されます。
たとえば、以下のような場合にはバッキングフィールドは存在しません:
val isEmpty: Boolean
get() = this.size == 0
バッキングプロパティ
「暗黙のバッキングフィールド」にそぐわないことをやりたい場合には、 バッキングプロパティ (backing property) を持つように必ずフォールバックさせることもできます:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() // 型パラメータが推論される
return _table ?: throw AssertionError("他スレッドによってnullをセットされた")
}
全ての点において、これはちょうどJavaと同じです。なぜなら、privateプロパティへデフォルトゲッターとセッターでのアクセスが、関数呼び出しのオーバヘッドが無いように最適化されているためです。
コンパイル時定数
値がコンパイル時にわかるプロパティは、 const
修飾子を使用して、 コンパイル時定数 (compile time constants) としてマークすることができます。
このようなプロパティは、次の要件を満たす必要があります:
- トップレベルまたは object のメンバ
- String 型の値またはプリミティブ型で初期化される
- カスタムゲッターが無い
このようなプロパティには、アノテーションを付けることができます:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
遅延初期化プロパティ
通常、非null型として宣言されたプロパティは、コンストラクタ内で初期化される必要があります。
しかし、かなり多くの場合において、これは便利ではありません。
たとえば、プロパティは、依存オブジェクト (DI; dependency injection; 依存性注入, 訳注:参考)を介して、またはユニットテストのセットアップメソッドで初期化することができます。
この事例では、非nullのイニシャライザをコンストラクタ内で提供することができませんが、それでもなおクラス内の本体にあるプロパティを参照する際にnullチェックを避けたいでしょう。
このような事例を扱うには、lateinit
修飾子でプロパティをマークすると良いでしょう:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 参照先を直に見に行く(dereference directly)
}
}
この識別子はクラス本体(プライマリコンストラクタでない)の中で宣言され、カスタムゲッターやカスタムセッターを持たない var
プロパティでのみ使用することができます。
プロパティの型が非nullかつ、プリミティブ型であってはなりません。
lateinit
プロパティが初期化される前にアクセスした場合、アクセスされたプロパティと、それが初期化されていないことを特定するための特別な例外が投げられます。
プロパティのオーバライド
メンバのオーバライド を参照してください。
委任プロパティ (Delegated Properties)
プロパティのうち最も一般的なのは、単純にバッキングフィールドからの読み込み(または書き込みかもしれない)です。
一方、カスタムゲッターとセッターを使えばプロパティの振る舞いを如何様にも実装できます。
いろんなところに、プロパティの動作について、確立された共通パターンがあります。
いくつかの例を挙げます:遅延評価値、与えられたキーでのmapの読み込み、データベースへのアクセス、アクセスをトリガとするリスナへの通知等。
このような一般的な振る舞いは、委譲プロパティ (delegated properties) を使ってライブラリに実装することができます。
インターフェース
Kotlinでのインタフェースは、Java 8と非常によく似ています。インタフェースは抽象メソッドの宣言と同様に、メソッドの実装を含めることができます。抽象クラスと違って、インタフェースは状態を持てません。インタフェースはプロパティを持つことができますが、これらは abstract であること、またはアクセサの実装を提供することが必要です。
インタフェースは、 interface{: .keyword } キーワードを使用して定義されます。
interface MyInterface {
fun bar()
fun foo() {
// 本体は任意
}
}
インタフェースの実装
クラスやオブジェクトは、1つまたは複数のインターフェイスを実装することができます:
class Child : MyInterface {
override fun bar() {
// 本体
}
}
インターフェイス内のプロパティ
インターフェイス内にプロパティを宣言することができます。インタフェースで宣言されたプロパティは、 abstract にすることも、アクセサの実装を提供することもできます。インタフェース内で宣言されたプロパティはバッキングフィールドを持つことはできず、それ故にインタフェース内で宣言されたアクセサはそれらを参照できません。
interface MyInterface {
val property: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(property)
}
}
class Child : MyInterface {
override val property: Int = 29
}
オーバーライドの競合解決
スーパータイプのリストでたくさんの型を宣言すると、同メソッドの複数の実装を継承するように見えることがあります。例えば:
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
}
インタフェース A と B は、両方とも関数 foo() と bar() を宣言しています。両方とも foo() を実装していますが、 B のみが bar() を実装しています。( bar() は A では abstract としてマークされていません。これは関数が本体を持たないときのインタフェースのデフォルトだからです。)
さて、もし具体クラス C を A から得れば、 bar() をオーバライドし、実装を提供しなければならないことは明らかです。そしてもし D を A と B から得れば、 bar() をオーバライドする必要はありません。なぜなら1つの実装を継承したからです。
しかし foo() の実装を2つ継承してしまったため、コンパイラはどっちを選んだら良いかわかりません。したがって foo() のオーバライドが強制され、何が欲しいのかを明示する必要があります。
可視性修飾子
クラス、オブジェクト、インタフェース、コンストラクタ、関数、プロパティとそのセッターは、可視性修飾子 (visibility modifiers) を持つことができます。(ゲッターは常にプロパティと同じ可視性を持ちます。)Kotlinには4つの可視性修飾子があります: private
, protected
, internal
, public
。明示的な修飾子がない場合に使用されるデフォルトの可視性は、public
です。
宣言スコープの違いは、後述の例をご覧ください。
パッケージ
関数、プロパティやクラス、オブジェクトやインターフェースは、「トップレベル」、つまり、パッケージ内部で直接宣言することができます。
// ファイル名: example.kt
package foo
fun baz() {}
class Bar {}
- 可視性修飾子を何も指定しない場合は、宣言がどこでも見える
public
がデフォルトして使用されます。 -
private
として宣言すると、その宣言を含むファイルの中でのみ見えます -
internal
として宣言すると、同じモジュール内のどこからでも見えます -
protected
はトップレベルの宣言では使用できません
例:
// ファイル名: example.kt
package foo
private fun foo() {} // example.kt の中で見える
public var bar: Int = 5 // プロパティはどこでも見える
private set // セッターは example.kt の中でのみ見える
internal val baz = 6 // 同じモジュール内でのみ見える
クラスとインタフェース
クラス内で宣言した場合:
-
private
はそのクラス内(そのすべてのメンバーを含む)でのみ見える -
protected
--private
と同じ + サブクラス内でも見えます -
internal
--internal
宣言するクラスを見る そのモジュール内の 任意のクライアントはそのinternal
メンバが見えます -
public
--public
宣言するクラスを見ている任意のクライアントは、public
のメンバが見えます
注意 Javaのユーザーへ:Kotlinでは、外部クラスはその内部クラスのprivate メンバが見えません。
protected
のメンバをオーバーライドして、明示的に可視性を指定しない場合、オーバーライドするメンバも、protected
の可視性になります。
例:
open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // デフォルトで public
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outer() {
// a は見えない
// b, c, d は見える
// Nested と e は見える
override val b = 5 // 'b' は protected
}
class Unrelated(o: Outer) {
// o.a, o.b は見えない
// o.c and o.d は見える(同じモジュール)
// Outer.Nested, Nested::e は見えない
}
コンストラクタ
クラスのプライマリコンストラクタの可視性を指定したい場合は、次の構文を使ってください(明示的に constructor{: .keyword }* キーワードを付加しなければならないことに注意):
class C private constructor(a: Int) { ... }
ここでは、コンストラクタは private
です。デフォルトでは、すべてのコンストラクタが public
です。これにより、そのクラスが見える場所であればどこからでもそのクラスを見ることができます。(すなわち、 internal
クラスのコンストラクタは、同じモジュール内でのみ見えます)
ローカル宣言
ローカル変数、関数やクラスは、可視性修飾子を持つことはできません。
モジュール
internal
可視性修飾子には、メンバが同じモジュールで見えることを意味します。具体的には、モジュールはKotlinのファイルセットであり、一緒にコンパイルされます。
- IntelliJ IDEAモジュール
- MavenやGradleのプロジェクト
- <kotlinc>Antタスクの1回の呼び出しでコンパイルされたファイルのセット
拡張 (extension)
Kotlinは、C#やGosuと似ていて、クラスを継承したりDecoratorのようなデザインパターンを使用せずとも、クラスを新しい機能で拡張する能力を提供します。
これは、 拡張 と呼ばれる特別な宣言を介して行われます。Kotlinは、 拡張関数 と 拡張プロパティ をサポートしています。
拡張関数
拡張関数を宣言するには レシーバタイプ (receiver type) を関数名の前に付ける必要があります。
次の例では、 swap
関数を MutableList<Int>
に追加しています:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' がリストに対応する
this[index1] = this[index2]
this[index2] = tmp
}
拡張関数内での this{: .keyword } キーワードは、レシーバオブジェクト(ドットの前に渡されたもの)に対応しています。これで、この関数を任意の MutableList<Int>
からでも呼べるようになりました:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'swap()' 中の 'this' は値 '1' を保持する
もちろん、任意の MutableList<T>
についてこの関数は理にかなっており、ジェネリックにもできます:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' はリストに対応する
this[index1] = this[index2]
this[index2] = tmp
}
関数名の前でジェネリック型のパラメータを宣言すると、レシーバ型の式で使用できるようになります。ジェネリック関数を参照してください。
拡張は 静的 に解決される
拡張機能は拡張したクラスを実際に変更するわけではありません。拡張を定義すると、クラスに新たなメンバを挿入するのではなく、そのクラスのインスタンスにおいて、ただ単にその新しい関数をただドット付きで呼べるようになるだけです。
拡張関数は 静的に 処理される、つまり、それらはレシーバの型による仮の存在ではないということを強調しておきたいです。これは、関数が呼び出されたときの式の型によって、呼び出される拡張関数が決定されるのであって、実行時の式の評価によるのではないことを意味します。例えば:
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
この例では、 "c"を出力します。呼び出されている拡張関数は C
クラスのパラメータ c
の宣言型にのみ依存するためです。
もし、あるクラスがメンバ関数を持つうえ、さらに、同じレシーバ型、同じ名前を有し、与えられた引数を受容可能な拡張関数が宣言されると、 常にメンバが優先されます 。例えば:
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
C
型の任意の c
から c.foo()
を呼べば、"extension" ではなく、 "member" と表示します。
しかしながら、異なる署名を持つが同名のメンバ関数を拡張関数がオーバライドすることは全く問題ありません:
class C {
fun foo() { println("member") }
}
fun C.foo(i: Int) { println("extension") }
C().foo(1)
の呼び出しで "extension" を表示します。
Null許容レシーバー
拡張は、null許容なレシーバの型で定義できることに注意してください。このような拡張は、その値がnullの場合でも、オブジェクト変数で呼び出すことができ、かつその本体内で this == null
をチェックすることができます。これにより、null をチェックせずに Kotlin で toString() を呼び出すことができます。チェックは拡張関数内で行われます。
fun Any?.toString(): String {
if (this == null) return "null"
// nullチェックの後だと、 'this' は非null型に自動キャストされるので、
// 下記の toString() は Any クラスのメンバであると解決される
return toString()
}
拡張プロパティ
関数と同様、Kotlinは拡張プロパティをサポートしています。
val <T> List<T>.lastIndex: Int
get() = size - 1
注意してほしいのは、拡張機能は、実際にはクラスにメンバを挿入しないので、拡張プロパティがバッキングフィールドを持つ効率的な方法がない、ということです。これが 初期化子が、拡張プロパティでは許可されていない 理由です。この挙動は、明示的にゲッター/セッターを作ることによって定義することのみができます。
例:
val Foo.bar = 1 // エラー:初期化子は拡張プロパティでは許可されていない
コンパニオンオブジェクトの拡張機能
クラスにコンパニオンオブジェクトが定義されている場合は、コンパニオンオブジェクトの拡張関数とプロパティを定義することもできます。
class MyClass {
companion object { } // "Companion" と呼ばれる
}
fun MyClass.Companion.foo() {
// ...
}
ちょうどコンパニオンオブジェクトの普通のメンバと同じように、それらは修飾子としてクラス名のみを使用して呼び出すことができます。
MyClass.foo()
拡張関数のスコープ
ほとんどの場合、トップレベル、すなわちパッケージ直下に拡張を定義します:
-->
package foo.bar
fun Baz.goo() { ... }
そのような拡張を宣言しているパッケージの外で使用するには、それを呼び出し箇所でインポートする必要があります:
package com.example.usage
import foo.bar.goo // "goo" という名前で全ての拡張をインポートする
// または
import foo.bar.* // "foo.bar" から全てインポートする
fun usage(baz: Baz) {
baz.goo()
)
詳細については、インポートを参照してください。
メンバとして拡張関数を宣言
クラス内では、別のクラスの拡張を宣言することができます。そのような拡張の中には、複数の _暗黙的なレシーバ_があります。修飾子なしでアクセスできるオブジェクトのメンバです。拡張が宣言されているクラスのインスタンスは ディスパッチレシーバ (dispatch receiver) と呼ばれ、拡張関数のレシーバ型のインスタンスは 拡張レシーバ と呼ばれます。
class D {
fun bar() { ... }
}
class C {
fun baz() { ... }
fun D.foo() {
bar() // D.bar を呼ぶ
baz() // C.baz を呼ぶ
}
fun caller(d: D) {
d.foo() // 拡張関数を呼ぶ
}
}
ディスパッチレシーバのメンバーと拡張レシーバの名前が衝突する場合には、拡張レシーバが優先されます。ディスパッチレシーバのメンバを参照するには、修飾子付き this
の構文を使用することができます。
class C {
fun D.foo() {
toString() // D.toString() を呼ぶ
this@C.toString() // C.toString() を呼ぶ
}
メンバとして宣言された拡張関数は、 open
として宣言され、サブクラスでオーバーライドすることができます。これは、そのような関数のディスパッチは、ディスパッチレシーバ型に関しては仮想的であるが、拡張レシーバ型に関しては静的であることを意味する。
open class D {
}
class D1 : D() {
}
open class C {
open fun D.foo() {
println("D.foo in C")
}
open fun D1.foo() {
println("D1.foo in C")
}
fun caller(d: D) {
d.foo() // 拡張関数を呼ぶ
}
}
class C1 : C() {
override fun D.foo() {
println("D.foo in C1")
}
override fun D1.foo() {
println("D1.foo in C1")
}
}
C().caller(D()) // 出力: "D.foo in C"
C1().caller(D()) // 出力: "D.foo in C1" - ディスパッチレシーバは仮想的に解決される
C().caller(D1()) // 出力: "D.foo in C" - 拡張レシーバは静的に解決される
動機
Javaでは、 "*Utils" という名前のクラス( FileUtils
、 StringUtils
など)をよく使っていました。有名な java.util.Collections
は、同じ品種に属します。そして、これらのUtils-クラスについての不快な部分は、それらを使用するコードが次のようになることです:
// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
これらのクラス名は常に邪魔になってきます。static インポートを使用すると、これをこうすることができます:
// Java
swap(list, binarySearch(list, max(otherList)), max(list))
これは少しだけマシですが、IDEの強力なコード補完の助けを全くまたはほんの少ししか得られません。次のようにできるならば、それはとても良いでしょう:
// Java
list.swap(list.binarySearch(otherList.max()), list.max())
でも、 List
クラスの中に考えられるすべてのメソッドを実装したいというわけではありませんよね?このようなときに拡張が私たちを助けてくれます。
※権利関係について
リファレンスのオリジナルを作ったJetBrain社はContributing to Kotlinで、「あなたの言語に翻訳し、Webサイト上で閲覧可能にすることを歓迎する。」 ( You are welcome to translate the Kotlin documentation into your own language and to publish your translation on your Web site. ) と宣言しています。
また、オリジナルはgithub上のプロジェクトで管理されており、ライセンス表示がApache License ver. 2.0で宣言されています。従って本ドキュメントもそれにならって、ライセンスをApache License ver. 2.0とします。