基本構文
基本概念
- パッケージとインポート (Packages and Imports):コードを名前空間に整理します。
main関数:Kotlin アプリケーションのエントリーポイントです。文字列の配列を引数として受け取れます。- 標準出力への出力 (Printing to Standard Output):
print()とprintln()は情報を表示します。println()は出力後に改行を追加します。 - 標準入力からの読み込み (Reading from Standard Input):
readln()はコンソールから1行のテキストを読み取ります。 - 関数 (Functions):
funキーワードで定義します。式本体と型推論による戻り値型を利用できます。意味のある値を返さない関数にはUnit戻り値型を使います。 - 変数 (Variables):
val:不変(読み取り専用)の変数。var:可変の変数。- 型推論:Kotlin は通常、型を自動的に決定できます。
- クラスとインスタンス (Classes and Instances):
classキーワードで定義します。プロパティは宣言部分またはクラス本体に定義できます。継承にはコロン:を使います。クラスは既定でfinalであり、openを付けると継承できます。 - コメント (Comments):1行コメント (
//) と複数行コメント (/* ... */) があります。複数行コメントはネストできます。 - 文字列テンプレート (String Templates):
$variableまたは${expression}を使って、変数や式をそのまま文字列に埋め込みます。 - 条件式 (Conditional Expressions):
if文です。ifは式としても使えます。 - ループ (Loops):
forループ(コレクションや範囲を反復処理)とwhileループがあります。 when式:より柔軟なswitch文に相当します。- 範囲 (Ranges):
in演算子で値が範囲内かどうかを確認します。forで範囲を反復処理できます。 - コレクション (Collections):
forで反復処理し、inで要素の所属を確認します。フィルタリング、マッピングなどには lambda 式を使います。 - Nullable 値と null チェック (Nullable Values and Null Checks):
?を付けて型を nullable にします。Nullable 値を使う前にnullを確認します。 - 型チェックと自動キャスト (Type Checks and Automatic Casts):
is演算子で変数の型を確認します。Kotlin はifブロック内で変数を検査した型へ自動的にキャストします。
よく使う構文
DTO(POJO/POCO)の作成
data class Customer(val name: String, val email: String)
data classはgetters(varの場合はsetters)、equals()、hashCode()、toString()、copy()、以及componentN()関数(分解用)を自動生成します。データクラスの作成が大幅に簡略になります。
関数パラメータのデフォルト値
fun foo(a: Int = 0, b: String = "") { ... }
- 関数定義時にパラメータのデフォルト値を指定できます。関数を呼び出すときは、デフォルト値を持つ引数を省略できます。
リストのフィルタリング
val positives = list.filter { x -> x > 0 }
val positives = list.filter { it > 0 } // より簡潔
filter関数と lambda 式を使い、特定の条件を満たす要素だけを含む新しいリストを作成します。itは、lambda 式の引数が1つだけの場合の暗黙的な名前です。
集合に要素が含まれるかの確認
if ("john@example.com" in emailsList) { ... }
if ("jane@example.com" !in emailsList) { ... }
inと!in演算子を使うと、集合に特定の要素が存在するかを簡単に確認できます。
文字列補間
println("Name $name")
$記号を使うと、変数を変数連結なしでそのまま文字列に埋め込めます。
標準入力を安全に読み込む
toIntOrNull()は文字列を整数に変換し、失敗した場合はnullを返します。そのためNumberFormatExceptionを回避できます。
インスタンスの型チェック
when (x) {
is Foo -> ...
is Bar -> ...
else -> ...
}
is演算子でオブジェクトの型を確認します。when式では、チェックが成功するとオブジェクトが 해당型に自動的にキャストされます。
読み取り専用リスト
val list = listOf("a", "b", "c")
listOf()は不変のリストを作成します。
読み取り専用マップ
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
mapOf()は不変のマップを作成します。
マップの項目 accessed
println(map["key"])
map["key"] = value // 可変マップの場合
- 角括弧
[]を使ってマップ内の値にアクセスします。
マップまたは Pair のリストを走査
for ((k, v) in map) {
println("$k -> $v")
}
- 分解宣言を使って、マップまたは
Pairのリストを走査します。kとvには、都合のよい名前を付けられます。
範囲の走査
..で閉区間、..<で開区間を作成します。stepはステップを指定し、downToは降順の範囲を作成します。
遅延プロパティ
val p: String by lazy { // 最初にアクセスしたときだけ値を計算する
// 文字列を計算
}
lazyデリゲートは、プロパティを最初にアクセスしたときだけ初期化します。計算コストの高いプロパティでは特にパフォーマンスを向上できます。
拡張関数
fun String.spaceToCamelCase() { ... }
"Convert this to camelcase".spaceToCamelCase()
- 元のクラスを継承または変更せずに、既存のクラスに新しい関数を追加できます。
シングルトンの作成
object Resource {
val name = "Name"
}
object宣言はシングルトンオブジェクトを作成します。Kotlin がシングルトンの作成と管理を自動的に処理します。
インライン値クラスによる型安全な値
@JvmInline
value class EmployeeId(private val id: String)
@JvmInline
value class CustomerId(private val id: String)
value class(JVM では@JvmInlineが必要)は、既存型のラッパークラスを作成し、コンパイル時の型安全性を提供します。CustomerIdを要求する関数にEmployeeIdを誤って渡すことを防ぎます。
抽象クラスのインスタンス化
- 匿名オブジェクトで抽象クラスをインスタンス化するには、すべての抽象メソッドを実装する必要があります。
null Shorthand
val files = File("Test").listFiles()
println(files?.size) // files が null でなければ size を表示
- 安全呼び出し演算子
?.を使うと、filesがnullの場合、式の結果はnullになり、NullPointerExceptionは発生しません。
null でなければ実行し、それ以外の場合は別の処理を実行する簡略記法
- Elvis 演算子
?:を使うと、左辺の式がnullの場合に返すデフォルト値を指定できます。runを使うと、コードブロックを実行してデフォルト値を計算できます。
null の場合に例外を送出する
val values = ...
val email = values["email"] ?: throw IllegalStateException("Email is missing!")
- Elvis 演算子
?:を使うと、values["email"]がnullの場合に例外を送出できます。
null になり得るコレクションの先頭項目を取得
val emails = ... // null になり得る
val mainEmail = emails.firstOrNull() ?: ""
firstOrNull()はコレクションの先頭の要素を返し、コレクションが空の場合はnullを返します。Elvis 演算子で、firstOrNull()がnullを返した場合のデフォルト値を指定できます。
null でなければ実行
val value = ...
value?.let {
... // value が null でなければこのコードブロックを実行
}
let関数は、値がnullでない場合だけ実行されます。itはletブロック内での値に対する暗黙的な名前です。
null でなければ nullable 値をマッピング
val value = ...
val mapped = value?.let { transformValue(it) } ?: defaultValue
// value または変換結果が null の場合は defaultValue を返す。
- 安全呼び出し演算子
?.、let関数、Elvis 演算子?:を組み合わせると、nullable 値を簡潔に変換し、値や変換結果が null の場合にデフォルト値を指定できます。
when 文で値を返す
when式は値を返すため、コードを簡潔にできます。
try-catch 式
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
// result を使用
}
try-catchブロックは値を返せるため、例外処理ロジックを式に埋め込めます。
if 式
val y = if (x == 1) {
"one"
} else if (x == 2) {
"two"
} else {
"other"
}
if文は値を返すため、条件に応じて異なる値を選べます。
Builder スタイルで Unit を返すメソッドを使う
fun arrayOfMinusOnes(size: Int): IntArray {
return IntArray(size).apply { fill(-1) }
}
apply関数を使うと、オブジェクト上で複数のメソッドを呼び出しながら、そのオブジェクト自身を返せます。Builder スタイルでオブジェクトを設定できます。
単一式の関数
- 関数が式を1つだけ含む場合は、中括弧と
returnキーワードを省略できます。
オブジェクトのインスタンスで複数のメソッドを呼ぶ (with)
with関数を使うと、同じオブジェクトで複数のメソッドを呼んでもオブジェクト名を繰り返す必要がありません。
オブジェクトのプロパティを設定 (apply)
val myRectangle = Rectangle().apply {
length = 4
breadth = 5
color = 0xFAFAFA
}
apply関数を使うと、オブジェクトのプロパティを設定しながら、そのオブジェクト自身を返せます。コンストラクターにないプロパティを設定する場合に便利です。
Java 7 の try-with-resources
val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use { reader ->
println(reader.readText())
}
use関数を使うと、ストリームなどのリソースが例外が発生した場合でも使用後に正しく閉じられます。
泛型型情報が必要な汎用関数
inline fun <reified T: Any> Gson.fromJson(json: JsonElement): T = this.fromJson(json, T::class.java)
reifiedキーワードを使うと、実行時に泛型型情報へアクセスできます。泛型の型を知る必要がある関数で便利です。
2つの変数を入れ替える
var a = 1
var b = 2
a = b.also { b = a }
also関数で代入前に操作を実行します。この例では、also関数がaの値をbに代入し、その後bの値をaに代入します。
データ型
型の一覧
- 数値 (Numbers):
Byte(8ビット)Short(16ビット)Int(32ビット)Long(64ビット)Float(32ビット)Double(64ビット)- 符号なし型:
UByte、UShort、UInt、ULong
- ブール値 (Booleans):
Boolean(true/false) - 文字 (Characters):
Char(Unicode) - 文字列 (Strings):
String(不変) - 配列 (Arrays):
Array<T>(可変)、特定型の配列:IntArray、ByteArrayなど
特別な型
Any:すべての非 null 型のスーパークラス(Java のObjectに相当)Nothing:「決して戻らない」型(例外を送出する関数など)Unit:「意味のある戻り値がない」型(Java のvoidに相当)
型チェックとキャスト
- 型チェック:
is - 安全キャスト:
as?(失敗するとnullを返す) - 安全でないキャスト:
as(例外を送出する可能性がある)
重要点
- Kotlin には原始型(primitive types)がありません。
- 型推論が強力なので、通常は型を明示的に宣言する必要がありません。
例
val age: Int = 30
val name = "Alice" // String と型推論される
val pi = 3.14 // Double と型推論される
制御構文
if 式
if (条件) 結果 else 結果(結果がUnitでなければelseは必須)- 三項演算子と同様に値を返せます。
val max = if (a > b) a else b // a または b の大きい方を返す
when 文/式
switchの代替。when (変数) { 値 -> 結果 ... else -> 結果 }- 式として使う場合は、すべての場合を網羅する必要があります(または
elseが必要)。 - 型(
is)や範囲(in)を検査できます。
for ループ
for (item in 集合) { ... }- コレクションの要素を反復処理します。
for (i in 範囲) { ... }で数値範囲を反復処理します。
val numbers = listOf(1, 2, 3)
for (number in numbers) {
println(number)
}
for (i in 1..5) { // 1 から 5(5 を含む)
println(i)
}
while ループ
while (条件) { ... }は先に判定してから実行します。do { ... } while (条件)は先に実行してから判定します(少なくとも1回は実行)。
ループの制御
break:ループを抜けます。continue:今回のループの残りを飛ばします。
for (i in 1..10) {
if (i == 3) continue // i=3 の反復を飛ばす
if (i == 7) break // i=7 でループを終了
println(i)
}
ジャンプ式
return
- 既定では、最も近い囲 functions または匿名関数から戻ります。
break
- 最も近い囲 loops loopを終了します。
continue
- 最も近い囲 loops loopの次の反復に進みます。
これらの式の型は Nothing です。
ラベル (Labels)
Kotlin では、identifier@ の形式で式にラベルを付けられます。ラベルを使うと、break と continue の動作をより細かく制御できます。
loop@ for (i in 1..100) {
for (j in 1..100) {
if (...) break@loop // loop ラベルのループを抜ける
}
}
break@label:ラベルlabelが付いたループの後の実行位置へ移動します。continue@label:ラベルlabelが付いたループの次の反復へ移動します。
ラベルへの return (Return to Labels)
ネストされた関数(関数リテラル、ローカル関数、オブジェクト式)からは、限定付きの return 文を使って外側の関数から戻れます。主に lambda 式からの return に使います。
Lambda 式から return する
明示的なラベル
CodeBlock Loading...この例では、
return@litはlit@が付いた lambda 式から戻ります。暗黙的なラベル:より簡潔で、ラベル名は lambda 式が渡される関数名と同じになります。
CodeBlock Loading...この例では、
return@forEachはforEachに渡された lambda 式から戻ります。匿名関数:lambda 式の代わりに匿名関数を使えます。
return文は匿名関数自身から戻ります。CodeBlock Loading...匿名関数を使うと、効果は
continueに似ています。
break の動作を再現する
ネストした lambda 式と非局所 return を使うと、break に似た動作を再現できます。
注意
値を返す場合、パーサーはラベル付きの return 文を優先します。たとえば return@a 1 は「ラベル @a の位置で 1 を返す」と解釈され、「ラベル @a の式 (1) を返す」とは解釈されません。
クラスと継承
既定のスーパークラス (Default Superclass)
- Kotlin のすべてのクラスには、共通のスーパークラス
Anyがあります。 - クラスでスーパークラス型を宣言していない場合、
Anyが既定のスーパークラスになります。
class Example // Any を暗黙的に継承
Anyにはequals()、hashCode()、toString()の3つのメソッドがあります。これらはすべての Kotlin クラスで定義されています。
オープンクラス (Open Class)
- 既定では、Kotlin のクラスは
finalであり、継承できません。 - クラスを継承可能にするには、
openキーワードを付けます。
open class Base // 継承可能
明示的なスーパークラス型 (Explicit Supertype)
- クラスヘッダーでスーパークラス型を明示するには、コロンの後に型を書きます。
open class Base(p: Int)
class Derived(p: Int) : Base(p)
- 主コンストラクター (Primary Constructor):派生クラスに主コンストラクターがある場合、パラメーターに応じて主コンストラクターで基底クラスを初期化する必要があります。
- 副コンストラクター (Secondary Constructor):派生クラスに主コンストラクターがない場合、副コンストラクターごとに
superキーワードで基底型を初期化するか、初期化を行う別のコンストラクターへ委譲する必要があります。異なる副コンストラクターから基底型の異なるコンストラクターを呼べます。
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
メソッドのオーバーライド (Overriding Methods)
- Kotlin では、オーバーライド可能なメンバーと実際のオーバーライドに、明示的な修飾子が必要です。
open class Shape {
open fun draw() { /*...*/ }
fun fill() { /*...*/ }
}
class Circle() : Shape() {
override fun draw() { /*...*/ }
}
Circle.draw()にはoverride修飾子が必要です。省略するとコンパイルエラーになります。- 関数に
open修飾子がない場合(Shape.fill()など)、overrideの有無にかかわらず、サブクラスで同じシグネチャのメソッドを宣言できません。 finalクラスにメンバーを追加しても、open修飾子は効きません。overrideを付けたメンバー自体はopenなので、サブクラスでオーバーライドできます。- 再オーバーライドを防ぐには
finalを使います。
open class Rectangle() : Shape() {
final override fun draw() { /*...*/ }
}
プロパティのオーバーライド (Overriding Properties)
- プロパティのオーバーライドは、メソッドと同じ仕組みで機能します。
- スーパークラスで宣言し、派生クラスで再宣言するプロパティには
overrideを付け、互換性のある型同士でなければなりません。 - 宣言された各プロパティは、初期化子を持つプロパティ、または
getメソッドを持つプロパティでオーバーライドできます。
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4
}
valプロパティはvarプロパティでオーバーライドできますが、逆はできません。valプロパティは本质上getメソッドを宣言しており、varとしてオーバーライドすると派生クラスにsetメソッドも宣言されるためです。- 主コンストラクターでは、プロパティ宣言の一部として
overrideキーワードを使えます。
派生クラスの初期化順序 (Derived Class Initialization Order)
- 派生クラスの新しいインスタンスを生成する際、基底クラスの初期化は最初に行われます。ただし、基底コンストラクター引数の計算より前です。つまり、派生クラスの初期化ロジックが実行される前に行われます。
- 基底コンストラクターの実行時、派生クラスで宣言またはオーバーライドしたプロパティはまだ初期化されていません。基底クラスの初期化ロジック内でこれらのプロパティを直接使う、別の open メンバーを経由して間接的に使うと、不正な動作や実行時エラーにつながる可能性があります。
- 基底クラスを設計するときは、コンストラクター、プロパティ初期化子、
initブロックで open メンバーを使わないようにしてください。
スーパークラスの実装を呼ぶ (Calling the Superclass Implementation)
派生クラスのコードでは、super キーワードを使ってスーパークラスの関数やプロパティアクセサの実装を呼べます。
- 内部クラスから外部クラスのスーパークラスへアクセスするときは、外部クラス名で限定した
superキーワードを使います:super@Outer。
オーバーライドの規則 (Overriding Rules)
- Kotlin では、継承の実装は次の規則に従います。クラスが直接のスーパークラスから同じメンバーの複数の実装を継承した場合、そのメンバーをオーバーライドして独自の実装を提供する必要があります。継承した実装のいずれかを使うこともできます。
- 継承元の実装を持つスーパークラス型を示すには、
superを使い、スーパークラス名を山括弧で囲んで限定します。たとえばsuper<Base>です。
RectangleとPolygonの両方を継承できますが、それぞれにdraw()の実装があるため、Squareでdraw()をオーバーライドし、曖昧さを解消する別の実装を提供する必要があります。
ジェネリクス:in、out、where
ジェネリッククラス
- 宣言:山括弧
<>で型パラメーターを宣言します。
class Box<T>(t: T) {
var value = t
}
- インスタンス化:型パラメーターを指定するか、コンパイラーに推論させます。
val box: Box<Int> = Box<Int>(1) // 型を明示
val box = Box(1) // コンパイラーが Box<Int> と推論
変性 (Variance)
- 不変性 (Invariance):既定では、Kotlin のジェネリック型は不変です。つまり
List<String>はList<Object>のサブタイプではありません。 - 共変性 (Covariance):
outキーワードで共変な型パラメーターを宣言します。共変型パラメーターはoutの位置にしか現れません(戻り値としては使えて、引数としては使えません)。クラスCの型パラメーターTをoutと宣言すると、C<Base>はC<Derived>のスーパークラスとして安全に使えます。CはTの生産者であり、消費者ではないと理解できます。 - 反変性 (Contravariance):
inキーワードで逆変な型パラメーターを宣言します。逆変型パラメーターはinの位置にしか現れません(引数としては使えて、戻り値としては使えません)。List<? super String>のようにStringを引数に取るメソッドを呼べます。Tを返すメソッドを呼んだ場合、返るのはStringではなくObjectになります。 - 宣言箇所の変性 (Declaration-site Variance):型パラメーターの宣言箇所で
inとoutキーワードを使って変性を指定します。 - 使用箇所の変性 (Use-site Variance):型を使う箇所で、型投影を使って変性を指定します。
- PECS 原則 (Producer Extends, Consumer Super):
- オブジェクトが生産者(データを読むだけ)なら
outを使います(Java のextendsに相当)。 - オブジェクトが消費者(データを書くだけ)なら
inを使います(Java のsuperに相当)。
- オブジェクトが生産者(データを読むだけ)なら
out
Tを返すだけ
in
Tを受け取るだけ
型投影 (Type Projections)
- 概念:ジェネリック型の使い方を制限し、型安全性を確保します。
out投影:書き込みを禁止し、読み取りだけを許可します(Array<? extends Object>に相当)。in投影:読み取りを禁止し、書き込みだけを許可します(Array<? super String>に相当)。
スター投影 (Star-projections)
- 概念:型パラメーターについて何も知らない一方、安全な方法での使用は可能であることを表します。
Foo<out T : TUpper>はFoo<out TUpper>と同じ意味です(TUpper型の値は安全に読み取れます)。Foo<in T>はFoo<in Nothing>と同じ意味です(安全に書き込むことはできません)。Foo<T : TUpper>は、読み取り時はFoo<out TUpper>、書き込み時はFoo<in Nothing>と同じ意味です。- Java の原始型に似ていて、より安全です。
ジェネリック関数
- 宣言:型パラメーターを関数名の前に置きます。
fun <T> singletonList(item: T): List<T> { ... }
fun <T> T.basicToString(): String { ... } // 拡張関数
- 呼び出し:型パラメーターを明示するか、コンパイラーに推論させます。
val l = singletonList<Int>(1) // 型を明示
val l = singletonList(1) // コンパイラーが型を推論
ジェネリック制約 (Generic Constraints)
- 概念:型パラメーターの置き換え可能な型範囲を制限します。
- 上限 (Upper Bounds):コロン
:で上限を指定します。Java のextendsに似ています。
fun <T : Comparable<T>> sort(list: List<T>) { ... } // T は Comparable<T> のサブタイプでなければならない
where句:複数の上限が必要な場合はwhere句を使います。
明示的に非 null の型 (Definitely Non-nullable Types)
T & Anyを使って、型パラメーターを明示的に非 null の型として宣言します。- 通常、
@NotNull注釈が付いた Java メソッドとの相互運用に使います。
型消去 (Type Erasure)
- Kotlin はコンパイル時にジェネリック型の安全性を検査します。
- 実行時、ジェネリック型のインスタンスには、実際の型パラメーターに関する情報が含まれません。
Foo<Bar>とFoo<Baz?>のインスタンスは、実行時にはどちらもFoo<*>に消去されます。
ジェネリック型のチェックとキャスト
- 型消去のため、実行時にジェネリック型のインスタンスが特定の型パラメーターで作成されたか確認できません(たとえば、
ints is List<Int>は使えません)。 - スター投影型のインスタンスは型チェックできます(たとえば、
something is List<*>)。 - 型パラメーターが静的にチェック済みなら、型パラメーターを使わない非ジェネリック部分については型チェックやキャストができます(たとえば、
list as ArrayList)。 - ジェネリック関数呼び出しの型パラメーターはコンパイル時にのみ検査されます。関数本体の中では型パラメーターを型チェックに使えず、型パラメーターへのキャストも未検査になります。例外は。具体化された型パラメーターを持つインライン関数で、呼び出し元ごとに実際の型パラメーターがインラインされる場合です。
未検査のキャスト (Unchecked Casts)
- 具体的な型パラメーターを持つジェネリック型へのキャストは、実行時に検査できません(たとえば、
foo as List<String>)。 - 型の安全性が高度なプログラムロジックから示唆されるものの、コンパイラーが直接推論できない場合"These未検査のキャストを使えます。
@Suppress("UNCHECKED_CAST")を文や宣言に付けると、未検査キャストの警告を抑止できます。
型パラメーター用のアンダースコア演算子
- 型パラメーターにはアンダースコア演算子
_を使えます。他の型が明示的に指定されている場合、演算子を使ってパラメーターの型を自動推論できます。
データクラス (Data Classes)
基本概念
- Kotlin のデータクラスは主にデータを保持するために使います。各データクラスについて、コンパイラはインスタンスを人が読みやすい出力で出力したり、インスタンス同士を比較したり、インスタンスをコピーしたりする追加のメンバー関数を自動生成します。データクラスには
dataを付けます。
data class User(val name: String, val age: Int)
自動生成されるメンバー
- 主コンストラクターで宣言したすべてのプロパティに対して、コンパイラは次のメンバーを自動生成します。
equals()/hashCode()のペア。toString()の形式は "User(name=John, age=42)" です。- プロパティの宣言順に対応する
componentN()関数。 copy()関数。
データクラスの要件
- 生成コードの一貫性と意味のある動作を保証するため、データクラスは次の条件を満たす必要があります。
- 主コンストラクターには少なくとも1つのパラメーターが必要です。
- すべての主コンストラクターパラメーターを
valまたはvarにする必要があります。 - データクラスは
abstract、open、sealed、innerにできません。
メンバー継承の規則
- データクラスの本体で
equals()、hashCode()、toString()を明示的に実装した場合や、スーパークラスで最終実装を明示的に実装した場合、これらの関数は生成されず、既存の実装が使われます。 - スーパークラス型に
componentN()関数がある場合(それらはopenで、互換性のある型を返す場合)、データクラスには対応する関数が生成され、スーパークラスの関数をオーバーライドします。シグネチャが互換性がないか、関数がfinalのためオーバーライドできない場合は、エラーが報告されます。 componentN()とcopy()関数に明示的な実装を提供することはできません。- データクラスは他のクラスを継承できます。
JVM 向け引数なしコンストラクター
- JVM で、生成されたクラスに引数なしコンストラクターが必要な場合は、プロパティにデフォルト値を指定する必要があります。
data class User(val name: String = "", val age: Int = 0)
クラス本体で宣言するプロパティ
- コンパイラは自動生成される関数を作る際に、主コンストラクター内で定義されたプロパティだけを使います。生成された実装からプロパティを除くには、クラス本体で宣言します。
data class Person(val name: String) {
var age: Int = 0
}
- この例では、既定では
nameプロパティだけがtoString()、equals()、hashCode()、copy()の実装で使われ、コンポーネント関数もcomponent1()だけになります。ageプロパティはクラス本体で宣言されているため、対象外です。そのため、nameは同じでageの値が異なる2つのPersonオブジェクトは等しいと判定されます。equals()は主コンストラクターに由来するプロパティだけを評価するためです。
コピー (Copying)
copy()関数でオブジェクトをコピーし、他のプロパティを変えずに一部のプロパティだけを変更できます。上記のUserクラスの場合、この関数の実装は次のようになります。
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
データクラスと分解宣言 (Destructuring Declarations)
- データクラスに生成されるコンポーネント関数により、分解宣言で使えます。
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// Jane, 35 years of age
標準データクラス
- 標準ライブラリは
PairとTripleクラスを提供します。ただし,大多数の場合、名前付きデータクラスのほうが良い設計です。プロパティに意味のある名前を付けることで、コードが読みやすくなるためです。
列挙クラス (Enum Classes)
基本概念
- Kotlin の列挙クラスは、有限個の異なる値を表します。各値は列挙クラスのインスタンスです。
基本的な使い方
- 宣言:
enum classキーワードで列挙クラスを宣言します。
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
- 定数:列挙定数はクラスのオブジェクトで、カンマで区切ります。
初期化
- コンストラクター:列挙定数はクラスと同様に初期化でき、コンストラクターで引数を渡せます。
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
匿名クラス
- 定義:列挙定数は、それぞれ独自のメソッドや基底クラスのメソッドのオーバーライドを持つ匿名クラスを宣言できます。
- メンバーの定義:列挙クラスがメンバーを定義する場合、セミコロンで定数の定義とメンバーの定義を区切ります。
インターフェースの実装
- インターフェースの実装:列挙クラスはインターフェースを実装できますが、クラスは継承できません。すべてのエントリに共通の実装を提供したり、各エントリの匿名クラスに個別の実装を提供したりできます。
- 既定では、すべての列挙クラスは
Comparableインターフェースを実装します。列挙クラスの定数には自然な順序があり、比較できます。
列挙定数の使い方
Kotlin の列挙クラスには、定義済みの列挙定数を一覧表示する合成プロパティと、名前で列挙定数を取得する合成メソッドがあります。
EnumClass.valueOf(value: String): EnumClass:名前から列挙定数を返します。指定した名前がクラスで定義されたどの列挙定数とも一致しない場合、valueOf()メソッドはIllegalArgumentExceptionを送出します。EnumClass.entries: EnumEntries<EnumClass>:Kotlin 1.9 で導入され、専用のList<EnumClass>型の列挙項目を返します。Kotlin 2.0 では、配列を返すenumValues<T>()が置き換えられます。CodeBlock Loading...
各列挙定数には次のプロパティがあります。
name:列挙定数の名前を取得します。ordinal:列挙定数が列挙クラスの宣言内で占める位置を取得します(0 から始まります)。KOTLINprintln(RGB.RED.name) // prints RED println(RGB.RED.ordinal) // prints 0
ジェネリックな方法で列挙定数にアクセス
enumEntries<T>():Kotlin 2.0 で導入され、指定した列挙型Tのすべての列挙項目をリストで返します。推奨される方法であり,性能にも優れています。
インライン値クラス
基本概念
- インライン値クラスは、値を1つだけ保持する特殊なクラスです。実行時には実際のクラスインスタンスが作られず、メモリ使用量を減らしてパフォーマンスを向上できます。
宣言と使用
- 宣言:
value classキーワードでインライン値クラスを宣言します。JVM では@JvmInline注釈が必要です。
@JvmInline
value class Password(val value: String)
- 特徴:
- プロパティは1つだけ持て、コンストラクターで初期化する必要があります。
- パフォーマンス最適化:実行時にはインライン値クラスのインスタンスが元の値を直接使い、新しいオブジェクトは作られません。
- 型安全性:元となる型は
Stringですが、Password型をStringとして直接使うことはできず、逆もできません。typealiasとは異なります。typealiasは型の別名にすぎず,互相に置き換えられます。
使う場面
- 新しい型を作りたいが、パフォーマンスを犠牲にしたくない場合。
- 既存の型により強い制約を付けたい場合。
例
object キーワード
シングルトンオブジェクト (Singleton Objects)
- 定義:
objectキーワードは、インスタンスが1つだけ存在するシングルトンクラスを宣言するために使います。 - 特徴:
- シングルトンオブジェクトは最初にアクセスされたときに作成され、アプリケーションのライフサイクル全体を通じて存在します。
- シングルトンオブジェクトにはコンストラクターを持てません。
- クラスを継承したり、インターフェースを実装したりできます。
コンパニオンオブジェクト (Companion Objects)
- 定義:
companion objectキーワードで、クラス内にコンパニオンオブジェクトを宣言します。 - 特徴:
- コンパニオンオブジェクトはクラスに関連しますが、クラスのインスタンスではありません。
- コンパニオンオブジェクトはクラスの非公開メンバーにアクセスできます。
- 通常、クラスのファクトリーメソッドを作成したり、クラスの静的メンバーを保持したりするために使います。
匿名オブジェクト (Anonymous Objects)
- 定義:
object : ClassName()で、名前を持たない匿名オブジェクトを作成します。 - 特徴:
- 匿名オブジェクトは、インターフェースの実装やクラスの継承で、一度限りの実装を提供するためによく使われます。
- 外側のスコープの変数にアクセスできます。
- 直ちに実行され、初期化されます。
オブジェクト宣言とオブジェクト式の比較
| 特性 | Object 宣言 (Singleton) | Object 式 (匿名オブジェクト) |
|---|---|---|
| 用途 | シングルトンの作成 | 一度限りの使用 |
| 初期化タイミング | 初回アクセス時に遅延ロード | 即時に初期化 |
| アクセス方法 | 名前で直接アクセス | 式の結果(変数参照) |
| 命名 | 名前が必須 | 命名不要 |
パッケージ (Packages) とインポート (Imports)
基本概念
- パッケージ (Package):Kotlin のコードを整理する/folders に似たものです。各ソースファイルは、どのパッケージに属するかを宣言できます。
- インポート (Import):完全なパッケージ名を書かなくても、他のパッケージのクラスや関数などをコード内で使えるようにします。
重要情報
パッケージ宣言 (Package Declaration)
- ソースファイルの先頭で
packageキーワードを使ってパッケージ名を宣言します:package org.example - ソースファイル内のすべての内容(クラス、関数など)はそのパッケージに属します。
- 例:
org.example.printMessageとorg.example.Message - パッケージを指定しない場合は、既定パッケージ(名前なし)に属します。
- ソースファイルの先頭で
既定のインポート (Default Imports)
- Kotlin はよく使うパッケージを既定でインポートするため、手動でインポートする必要はありません。
kotlin.*、kotlin.annotation.*、kotlin.collections.*などが含まれます。- 対象プラットフォーム(JVM、JS)に応じて、
java.lang.*(JVM)やkotlin.js.*(JS)などのパッケージが追加でインポートされます。
カスタムインポート (Custom Imports)
importキーワードで、個々の名前かパッケージ全体をインポートできます。- 個々の名前をインポート:
import org.example.Message(Messageを直接使える) - パッケージ全体をインポート:
import org.example.*(org.example内のすべての内容を使える)
エイリアス (Alias)
- インポートした名前が競合する場合は、
asキーワードでインポート対象の名前を変更して、衝突を解消できます。 例:
KOTLINimport org.example.Message import org.test.Message as TestMessage // TestMessage を使って org.test.Message を参照できる
- インポートした名前が競合する場合は、
インポートできるもの
- トップレベル関数とプロパティ
- オブジェクト宣言内の関数とプロパティ
- 列挙定数
トップレベル宣言の可視性
- トップレベル宣言(トップレベル関数、トップレベル変数など)に
privateを付けると、その宣言を含むファイル内からのみアクセスできます。
- トップレベル宣言(トップレベル関数、トップレベル変数など)に
インターフェース (Interfaces)
基本概念
- Kotlin のインターフェースは Java 8 以降のインターフェースに似ており、抽象メソッドの宣言とメソッドの実装を含められます。重要な違いは、インターフェースが状態を保持できないことです。プロパティは持てますが、抽象的でなければならず,或者アクセサの実装を提供する必要があります。
インターフェースを定義する
interfaceキーワードでインターフェースを定義します。
インターフェースの実装
- クラスまたはオブジェクトは、1つ以上のインターフェースを実装できます。
class Child : MyInterface {
override fun bar() { // MyInterface の抽象メソッド bar() の実装が必要
// メソッド本体
}
}
- コロン (
:) で継承または実装の関係を示します。 - インターフェースのメソッドをオーバーライドするには、必ず
overrideキーワードを使います。
インターフェースのプロパティ
- インターフェースはプロパティを宣言できます。
- インターフェースのプロパティにはバッキングフィールドを持てません。
- インターフェースのプロパティは抽象的にするか、アクセサ(accessor)の実装を提供できます。
インターフェースの継承
- インターフェースは他のインターフェースを継承できます。
- 継承したインターフェースは、メンバーの実装を提供したり、新しい関数やプロパティを宣言したりできます。
- 実装クラスでは、不足している実装だけを定義します。
オーバーライドの競合を解決する
- 1つのクラスが複数のインターフェースを実装すると、同じメソッドの複数の実装を継承する場合があります。その場合、そのメソッドをどう実装するかを明示する必要があります。
- 同じシグネチャのメソッドを複数継承し、特定の実装 hierarchiesのインターフェースの実装を呼び出したい場合は、
super<インターフェース名>.メソッド名()を使って指定できます。 - 実装を1つだけ継承した場合(たとえば
bar())でも、クラスの動作を明確にするため、オーバーライドして実装を指定する必要があります。コンパイラーは、どのインターフェースの既定の実装を使うべきかを自動的に判断できません。オーバーライドしないとコンパイルエラーになります。
関数型 (SAM) インターフェース
基本概念
- 関数型インターフェース(Functional Interface)、別名単一抽象メソッド(Single Abstract Method, SAM)インターフェースとは、抽象メンバー関数が1つだけあるインターフェースです。抽象でないメンバー関数は複数持てますが、抽象メンバー関数は1つしか持てません。Kotlin には
fun interfaceキーワードがあり、関数型インターフェースを宣言できます。SAM 変換にも対応しているため、lambda 式でクラスを手動実装する代わりに使用でき、コードを簡潔にできます。
関数型インターフェースを宣言する
fun interfaceキーワードを使います。
fun interface KRunnable {
fun invoke() // 唯一の抽象メソッド
}
SAM 変換 (SAM Conversions)
- 概念:SAM 変換を使うと、関数型インターフェースを実装するクラスを手動作成する代わりに lambda 式を使えます。Kotlin は、インターフェースの単一抽象メソッドのシグネチャに一致する lambda 式を、自動的にインターフェースのインスタンスへ変換します。
- 利点:コードを簡略化し、読みやすくします。
- 例:
この例では、lambda 式
{ it % 2 == 0 }が自動的にIntPredicateインターフェースの実装へ変換されます。Java インターフェースも SAM 変換に対応
コンストラクターを持つインターフェースから関数型インターフェースへの移行
Kotlin 1.6.20 以降では、関数型インターフェースのコンストラクターへの呼び出し可能参照(callable references)をサポートしています。コンストラクター関数を持つインターフェースから関数型インターフェースへ移行するためのソース互換性の手段になります。
例:
- バイナリ互換性の維持:
@Deprecated注釈で、従来の関数Printerを非表示(DeprecationLevel.HIDDEN)にできます。
@Deprecated(
message = "使用関数型接口 Printer 代替",
level = DeprecationLevel.HIDDEN
)
fun Printer(...) {...}
関数型インターフェースと型エイリアス (Type Aliases)
型エイリアスも関数型の使用を簡略化できますが、関数型インターフェースと型エイリアスの目的は同じではありません。
型エイリアス:既存型の別名にすぎず、新しい型を作りません。
KOTLINtypealias IntPredicate = (i: Int) -> Boolean // IntPredicate は関数型 (i: Int) -> Boolean のエイリアスにすぎない関数型インターフェース:新しい型を作ります。
KOTLINfun interface IntPredicate { fun accept(i: Int): Boolean // IntPredicate は実際のインターフェース型 }
違いのまとめ
| 特性 | 型エイリアス | 関数型インターフェース |
|---|---|---|
| 型の作成 | 新しい型を作らない | 新しい型を作る |
| メンバー | メンバーを持てるのは関数型1つだけ | 抽象でないメンバー関数と抽象メンバー関数を1つずつ持てる |
| インターフェース拡張 | 他のインターフェースの実装や継承はサポートしない | 他のインターフェースの実装や継承をサポートする |
| 拡張関数 | 拡張関数は、同じシグネチャのすべての関数型に適用され、特定のエイリアスに限定されない。 | 拡張関数は、その関数型インターフェースのインスタンスにだけ適用され、他の関数型には影響しない。 |
| 使用場面 | API が特定のパラメーター型と戻り値型を持つ関数(任意の関数)を受け取る必要がある場合に使う。 | 関数型のシグネチャでは表せない、平凡でない約束や操作など、より複雑なエンティティを定義する。 |
| 性能 | オーバーヘッドが小さい | ある程度のオーバーヘッドがある。例えば specific interface への変換など |
選び方
- 型エイリアス:API が特定のパラメーター型と戻り値型を持つ関数(任意の関数)を受け取る必要がある場合は、型エイリアスまたは単純な関数型を使います。
- 関数型インターフェース:API が関数より複雑なエンティティ、たとえば関数型のシグネチャでは表せない、平凡でない約束や操作を持つものを受け取る必要がある場合は、関数型インターフェースを使います。
可視性修飾子
基本概念
- 可視性修飾子は、クラス、オブJECT、インターフェース、コンストラクター、関数、プロパティとその setter の公開範囲を制御します。Getter の可視性は常にプロパティと同じです。Kotlin には
private、protected、internal、publicの4種類の可視性修飾子があります。既定の可視性はpublicです。
可視性修飾子の概要
| 修飾子 | 説明 |
|---|---|
public | (既定)どこからでも見える。 |
private | 宣言したファイルまたはクラス内でのみ見える。 |
protected | 宣言したクラスとそのサブクラス内でのみ見える。 |
internal | 同一モジュール内でのみ見える。モジュールは一緒にコンパイルされる Kotlin ファイルのグループ。 |
パッケージレベルの宣言 (Package-level Declarations)
パッケージ内で直接宣言した関数、プロパティ、クラス、オブジェクト、インターフェースをトップレベル宣言と呼びます。
public(既定):どこからでも見える。private:宣言を含むファイル内でのみ見える。internal:同一モジュール内のどこからでも見える。protected:トップレベル宣言には適用できません。
- 別パッケージから可視なトップレベル宣言を使うには、インポートする必要があります。
クラスメンバー (Class Members)
クラス内部で宣言されたメンバーについては、次のようにします。
private:クラス内部でのみ見える(すべてのメンバーを含む)。protected:privateと同じ可視性ですが、サブクラスでも見える。internal:同一モジュール内で、宣言したクラスのクライアントなら誰でも見える。public:宣言したクラスのクライアントなら誰でも見える。
- 外部クラスから内部クラスの
privateメンバーは見えません。 protectedまたはinternalメンバーをオーバーライド場合に可視性を明示しなければ、オーバーライドしたメンバーも元のメンバーと同じ可視性になります。
コンストラクター (Constructors)
- 次の構文で、クラスの主コンストラクターの可視性を指定できます。
class C private constructor(a: Int) { ... } // コンストラクターは private
- 既定では、すべてのコンストラクターは
publicです。これは実質、クラスが見える場所ならどこでも見えることを意味します(つまりinternalクラスのコンストラクターは同一モジュール内でのみ見えます)。 - 密封クラス(sealed classes)のコンストラクターは、既定で
protectedです。
ローカル宣言 (Local Declarations)
- ローカル変数、関数、クラスには可視性修飾子を使えません。
モジュール (Modules)
internal可視性修飾子は、メンバーが同一モジュール内で可见であることを示します。具体的には、次のように一緒にコンパイルされる Kotlin ファイルのグループを指します。- IntelliJ IDEA モジュール。
- Maven プロジェクト。
- Gradle ソースセット(テストソースセットから
mainのinternal宣言へアクセスできる例外があります)。 - 単一の
<kotlinc>Ant タスクでコンパイルされるファイル群。
委譲プロパティ (Delegated Properties)
基本概念
- 委譲プロパティは、Strong な Kotlin の機能です。プロパティの
get()とset()メソッドを別のオブジェクト(委譲オブジェクト)へ委譲し、コードの再利用、汎用的な動作、プロパティの実装ロジックの簡略化ができます。遅延初期化、プロパティ変更の監視、データの保存など、一般的なプログラミングパターンにとても適しています。
基本構文
kotlin
コピー
var <プロパティ名>: <型> by <式>
<プロパティ名>:宣言するプロパティの名前。<型>:プロパティの型。委譲オブジェクトのgetValue()メソッドの戻り値型と一致する必要があります。<式>:委譲オブジェクトを返す式。このオブジェクトが実際のプロパティアクセスと変更のロジックを担当します。by:Kotlin に委譲機構を使うよう指示するキーワード。
委譲オブジェクトの要件
委譲オブジェクトは、特定シグネチャの
getValue()関数(valとvarの両方に必要)とsetValue()関数(varの場合のみ必要)を用意する必要があります。これらの関数にはoperatorを付けます。getValue() 関数(必須):
kotlin
コピー
KOTLINoperator fun getValue(thisRef: Any?, property: KProperty<*>): 戻り値型 { ... }thisRef:プロパティを含むオブジェクトへの参照(Any?)。トップレベルプロパティの場合はnullになります。この引数 lets、委譲が所属オブジェクトの状態にアクセスできます(ただし、一般には推奨されません)。property:委譲されたプロパティを表すKPropertyインスタンス(KProperty<*>)。名前(property.name)など、プロパティのメタデータ取得に使えます。戻り値型:委譲プロパティの型。getValue()はこの型か、そのサブタイプの値を返す必要があります。
setValue() 関数(
varプロパティのみ):KOTLINoperator fun setValue(thisRef: Any?, property: KProperty<*>, value: 新値型) { ... }thisRef:プロパティを含むオブジェクトへの参照(getValue()と同じ)。property:委譲されたプロパティを表すKPropertyインスタンス(getValue()と同じ)。value:プロパティに割り当てる新しい値(型はプロパティと同じ)。setValue()は、この値を対応するストレージへ保存する処理を実行する必要があります。新値型:委譲プロパティの型。setValue()はこの型か、その親タイプの引数を受け取る必要があります。
標準デリゲート
Kotlin 標準ライブラリは、kotlin.properties.Delegates と kotlin パッケージから利用できる、便利で定義済みの委譲 Intriguingly 企业提供します。
lazy():プロパティを遅延初期化します。値は、提供的 lambda 式によって最初にアクセスされたときだけ計算されます。その後のアクセスでは、キャッシュされた値を返します。CodeBlock Loading...LazyThreadSafetyMode:遅延初期化で複数スレッドからのアクセスをどう扱うかを制御します。SYNCHRONIZED(既定):値の初期化を行うスレッドが1つだけになるように保証します。スレッドセーフですが、パフォーマンスのオーバーヘッドが生じる可能性があります。PUBLICATION:複数のスレッドが初期化を試みられますが、どのスレッドの計算結果か1つだけが使われることを保証します。初期化の lambda 式が冪等なら、この選択肢を使えます。NONE:スレッド安全性を一切保証しません。プロパティを単一スレッドからのみアクセスするなら、パフォーマンス Familiesのためこの選択肢を使えます。
Delegates.observable():観測可能なプロパティを作成します。プロパティに新しい値が割り当てられるたびに、渡されたハンドラーが呼ばれます。CodeBlock Loading...- ハンドラー関数のシグネチャは
(KProperty<*>, oldValue, newValue) -> Unitです。
- ハンドラー関数のシグネチャは
Delegates.vetoable():拒否可能なプロパティを作成します。渡されたハンドラーは、プロパティに新しい値を割り当て tries する前に呼ばれます。ハンドラーがtrueを返すと代入は行われ、falseを返すと代入は拒否されます。CodeBlock Loading...- ハンドラー関数のシグネチャは
(KProperty<*>, oldValue, newValue) -> Booleanです。
- ハンドラー関数のシグネチャは
別のプロパティへ委譲する
Kotlin では、プロパティの getter と setter を、同じクラスまたは別のクラスの別のプロパティへ委譲できます。向后互換性を保つためのリネームや、複数のプロパティを同期する場合に便利です。
- この例では、
delegatedToMemberの getter と setter の両方がmemberIntへ委譲されます。delegatedToMemberを変更するとmemberIntにも影響し、その逆も同様です。 @Deprecated:ユーザーにはoldNameではなくnewNameを使うよう促します。
プロパティを Map に保存する
Map または MutableMap を委譲オブジェクトとして使い、文字列キーを使って Map からプロパティ値を動的に取得できます。JSON やその他の動的なデータ構造を扱うときに便利です。
- この例では、
UserクラスのコンストラクターがMap<String, Any?>を受け取ります。nameとageプロパティの値は、この map から文字列キー"name"と"age"を使って取得されます。
ローカル委譲プロパティ
ローカル委譲プロパティを使うと、関数やコードブロックの内側で委譲プロパティを宣言できます。計算結果をキャッシュしたり、特定の場合にだけ必要なローカル変数を遅延初期化したりするのに便利です。
- この例では、
processedDataはdataがnullではなく、空文字列でもない場合にだけ初期化されます。dataがnullまたは空なら、初期化の lambda 式は実行されません。
provideDelegate 関数
provideDelegate 関数を使うと、委譲プロパティの作成過程をインターセプトできます。プロパティの正しい使い方を検証したり、カスタムの委譲インスタンスを設定したり、他の初期化ロジックを実行したりするのに便利です。
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): PropertyDelegate { ... }
thisRef:プロパティを含むオブジェクトへの参照(getValue()と同じ)。property:委譲されたプロパティを表すKPropertyインスタンス(getValue()と同じ)。- 戻り値:プロパティ委譲の実際のオブジェクトとして使われるオブジェクト。元の委譲オブジェクトとは異なるもの也行えます。ユーザーが指定した Delegate をカスタム Delegate に置き換えられます。
provideDelegate関数はプロパティの作成時に呼ばれ、実際の委譲オブジェクトが使われる前にロジックを実行できます。例では、プロパティとその ID がバインドされたことを示すメッセージを出力し、その後で実際のResourceDelegateを作成します。ResourceDelegateがリソースを遅延読み込みします。
委譲プロパティのコンパイル時処理
Kotlin コンパイラが委譲プロパティを処理すると、次の補助コードが生成されます。
- 非表示の
$delegateプロパティ:var prop: Type by MyDelegate()の場合、コンパイラはprop$delegateのような非表示のプロパティを作成します。型はMyDelegateで、実際の委譲オブジェクトを保持します。 getter と setter メソッド:コンパイラは
propの getter と setter を生成します。これらのメソッドは、委譲オブジェクトのgetValue()とsetValue()メソッドを呼び出します。CodeBlock Loading...最適化される場合:別のプロパティへ直接委譲する場合など、特定の場合には、コンパイラがパフォーマンスを最適化して、委譲プロパティのバッキングフィールドへ直接アクセスし、
$delegateプロパティの生成を省くことがあります。
拡張 (Extensions)
基本概念
- Kotlin の拡張を使うと、継承やデコレーターなどの設計パターンを使わずに、クラスやインターフェースに新しい機能を追加できます。特別な宣言(拡張)によって実現します。サードパーティライブラリのクラスやインターフェースに新しい関数を、元のコードを変更せずに書けます。
拡張関数 (Extension Functions)
- 宣言:関数名の前にレシーバー型を付けます。
拡張関数では、
thisキーワードがレシーバーオブジェクト(ドットより前に渡されたオブジェクト)を指します。呼び出し:通常のメソッドと同じように拡張関数を呼びます。
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // 'swap()' 内部の 'this' には 'list' の値が入る
拡張の静的解決
- 拡張は実際には拡張対象のクラスを変更しません。拡張を定義してもクラスに新しいメンバーが追加されるわけではなく、変数でドット記法を使って新しい関数を呼び出せるようになるだけです。
- 拡張関数は静的にディスパッチされます。コンパイル時にレシーバー型に基づき、どの拡張関数を呼ぶかが決まります。
- メンバーが優先:クラスにメンバー関数があり、同じレシーバー型・同じ名前で、指定した引数に適用できる拡張関数も定義されている場合、メンバー関数が常に優先されます。
- 同じ名前でシグネチャが異なるメンバー関数はオーバーロードできます。
Nullable レシーバー (Nullable Receiver)
- Nullable なレシーバー型の拡張を定義できます。これらの拡張は、オブジェクト変数の値が
nullのときでも呼び出せます。レシーバーがnullなら、thisもnullです。コンパイルエラーを避けるため、関数本体内でthis == nullを確認することを推奨します。
拡張プロパティ (Extension Properties)
- Kotlin は関数に似た拡張プロパティもサポートしています。
val <T> List<T>.lastIndex: Int
get() = size - 1
- 拡張は実際にはクラスにメンバーを挿入しないため、拡張プロパティにバッキングフィールド(backing field)を持たせることはできません。这也是、拡張プロパティで初期化子を使えない理由です。拡張プロパティの動作は、getter や setter を明示的に提供することでのみ定義できます。
class House
val House.number = 1 // エラー:拡張プロパティの初期化は許可されていない
コンパニオンオブジェクトへの拡張 (Companion Object Extensions)
- クラスがコンパニオンオブジェクトを定義している場合、そのコンパニオンオブジェクトにも拡張関数と拡張プロパティを定義できます。通常のコンパニオンオブジェクトのメンバーと同じように、クラス名だけを修飾子として使って呼び出せます。
拡張のスコープ (Scope of Extensions)
- ほとんどの場合、拡張はトップレベル(パッケージの直下)で定義します。
package org.example.declarations
fun List<String>.getLongestString() { /*...*/ }
- 宣言したパッケージの外で拡張を使うには、使用箇所でインポートします。
メンバーとしての拡張宣言 (Declaring Extensions as Members)
- クラスの中に別のクラスの拡張を宣言できます。このような拡張には複数の暗黙のレシーバーがあり、限定子なしで成员にアクセスできるオブジェクトがあります。拡張を宣言したクラスのインスタンスはディスパッチレシーバー、拡張メソッドのレシーバー型のインスタンスは拡張レシーバーと呼びます。
- ディスパッチレシーバーのメンバーと拡張レシーバーのメンバーで名前が衝突した場合は、拡張レシーバーが優先されます。ディスパッチレシーバーのメンバーを参照するには、限定付きの
this構文を使います。
- メンバーとして宣言した拡張は
openにして、サブクラスでオーバーライドできます。この場合、ディスパッチレシーバーの型に対するディスパッチは仮想ですが、拡張レシーバーの型に対しては静的です。
可視性に関する注意
- 拡張には、同じスコープで宣言された通常の関数と同じ可視性修飾子を使います。例:
- ファイルのトップレベルで宣言した拡張は、同じファイル内の他の
privateトップレベル宣言にアクセスできます。 - 拡張がレシーバー型の外側で宣言されている場合、レシーバーの
privateまたはprotectedメンバーにはアクセスできません。
- ファイルのトップレベルで宣言した拡張は、同じファイル内の他の
型エイリアス (Type Aliases)
基本概念
- 型エイリアスは、型に「別の名前」を付けるようなものです。「別の名前」を使っても正式な名前を使っても効果は同じですが、「別の名前」の方が短く、覚えやすい場合があります。
用途
長い型名の簡略化:
Set<Network.Node>のような長い型には、NodeSetのような短い名前を付けられます。複雑なジェネリック型を扱うとき特に便利です。関数型へのエイリアス:
(Int, String, Any) -> Unitのような関数型には、MyHandlerという名前を付けられます。内部クラス/ネストクラスへのエイリアス:複数のクラスが内部クラスを持つ場合、
A.InnerとB.InnerをそれぞれAInnerとBInnerで表すようにすると、区別しやすくなります。
例
重要:型エイリアスは新しい型ではない!
typealias Predicate<T> = (T) -> Booleanは、単に(T) -> Booleanに別名を付けただけです。- コンパイラーはコンパイル時に、すべての型エイリアスを対応する元の型へ置き換えます。
- つまり、エイリアス型を元の型と自由にお互いに置き換えられます。Kotlin コンパイラーは両者を完全に同じものとして扱います。
详细な例
関数 (Functions)
基本概念
- 関数はコードの基本的な構成要素で、特定の処理を実行します。Kotlin は、関数を定義して使う灵活な方法を提供し���。
関数宣言
- Kotlin の関数は
funキーワードで宣言します。 - 宣言形式:
fun 関数名(パラメーター名: パラメーター型): 戻り値型 { 関数本体 }
fun double(x: Int): Int {
return 2 * x
}
関数の使用(呼び出し)
- 標準的な方法で関数を呼びます:
関数名(引数) - メンバー関数(クラスやオブジェクト内の関数)はドット記法で呼びます:
インスタンス.関数名(引数)
val result = double(2) // double 関数を呼ぶ
Stream().read() // Stream クラスのインスタンスを作成し、read 関数を呼ぶ
パラメーター
- 関数パラメーターは Pascal 形式で定義します:
パラメーター名: パラメーター型 - 複数のパラメーターはカンマで区切ります。
- 各パラメーターには必ず型を明示的に指定する必要があります。
fun powerOf(number: Int, exponent: Int): Int { /* 関数本体 */ }
- 末尾カンマを使って関数パラメーターを宣言できます。
fun powerOf(
number: Int,
exponent: Int, // 末尾カンマ
) { /* 関数本体 */ }
デフォルト引数
- 関数パラメーターにはデフォルト値を持たせられます。関数を呼び出すときに該当引数を省略すると、デフォルト値が使われます。これにより、関数のオーバーロード数を減らせます。
- デフォルト値は、型の後に
=を追加して設定します。たとえばパラメーター名: パラメーター型 = デフォルト値です。
fun read(
b: ByteArray,
off: Int = 0,
len: Int = b.size,
) { /* 関数本体 */ }
- オーバーライドしたメソッドは、必ず基底メソッドのデフォルト引数値を使います。デフォルト引数を持つメソッドをオーバーライドする場合、デフォルト引数値はシグネチャから省略しなければなりません。
open class A {
open fun foo(i: Int = 10) { /*...*/ }
}
class B : A() {
override fun foo(i: Int) { /*...*/ } // デフォルト値は設定できない
}
- デフォルト値のないパラメーターより前にデフォルト値を持つパラメーターがある場合、そのデフォルト値は名前付き引数を使って関数を呼び出す場合だけ利用できます。
fun foo(
bar: Int = 0,
baz: Int,
) { /*...*/ }
foo(baz = 1) // デフォルト値 bar = 0 を使う
- デフォルト引数がある最後のパラメーターが lambda 式なら、名前付き引数として渡すか、括弧の外で渡せます。
名前付き引数
- 関数呼び出しでは、1つ以上の引数に名前を付けられます。引数众多で値と引数の対応が分かりにくいとき、特に引数がブール値や null 値のときに便利です。
- 関数呼び出しで名前付き引数を使うと、一覧での順序を自由に変更できます。デフォルト値を使いたい場合は、その引数を完全に省略できます。
- 4つの引数にデフォルト値を持つ
reformat()関数を考えます。
- この関数を呼び出す際、すべての引数に名前を付ける必要はありません。
reformat(
"String!",
false,
upperCaseFirstLetter = false,
divideByCamelHumps = true,
'_'
)
- デフォルト値を持つ引数をすべて省略できます。
reformat("This is a long String!")
- デフォルト値を持つ引数をすべて省略するだけでなく、特定のものだけ省略することもできます。ただし、最初に省略した引数より後の引数はすべて名前を付ける必要があります。
reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_')
- spread 演算子(配列の前置に
*を付ける)を使い、名前付き的可変長引数(vararg)を渡せます。
fun foo(vararg strings: String) { /*...*/ }
foo(strings = *arrayOf("a", "b", "c"))
- JVM 上で Java 関数を呼び出す場合、Java バイトコードが関数引数の名前を常に保持するわけではないため、名前付き引数構文を使えません。
Unit を返す関数
- 関数が有用な値を返さない場合、戻り値型は
Unitです。Unitは値Unitだけを持つ型で、この値を明示的に返す必要はありません。
Unit戻り値型の宣言も省略できます。上記のコードは次と同じです。
fun printHello(name: String?) { ... }
単一式の関数
- 関数本体が単一式で 구성されていれば、中括弧を省略し、
=の後ろに本体を書くことができます。
fun double(x: Int): Int = x * 2
- コンパイラーが戻り値型を推論できる場合、戻り値型を明示 declared する optional です。
fun double(x: Int) = x * 2
明示的な戻り値型
- ブロックで書かれた関数本体を持つ関数では、戻り値型を必ず明示する必要があります。ただし、
Unitを返す意図なら省略できます。 - Kotlin は、ブロックで書かれた関数本体の戻り値型を推論しません。この種の関数の本体には複雑な制御フローが含まれ得るとともに、戻り値型が読者や、場合によってはコンパイラーの肾病にも明らかではないためです。
可変個数の引数 (Varargs)
vararg修飾子で関数の引数(通常は最後の引数)を指定できます。
- この場合、関数に可変個数の引数を渡せます。
val list = asList(1, 2, 3)
- 関数内部では、型
Tのvararg引数はTの配列として扱われます。上記の例では、ts変数の型はArray<out T>です。 - 引数を
varargに指定できるのは1つだけです。vararg引数が最後の引数でない場合、後続の引数の値は名前付き引数構文で渡す必要があります。引数が関数型なら、括弧の外で lambda 式として渡すこともできます。 vararg関数を呼び出すときは、引数を個別に渡せます。たとえばasList(1, 2, 3)です。すでに配列があり、その内容を関数へ渡したい場合は、spread 演算子(配列の前置に*を付ける)を使います。
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
- プリミティブ型の配列を
varargに渡す場合は、toTypedArray()関数で通常の型付き配列へ変換する必要があります。
val a = intArrayOf(1, 2, 3) // IntArray はプリミティブ型の配列
val list = asList(-1, 0, *a.toTypedArray(), 4)
中置記法 (Infix Notation)
infixキーワードを付けた関数は、呼び出し時のドットと括弧を省略した中置記法でも呼べます。中置関数には次の条件があります。- メンバー関数または拡張関数でなければならない。
- 引数が1つだけなければならない。
- その引数は可変個数の引数を受け取れるものでなく、デフォルト値も持てない。
infix fun Int.shl(x: Int): Int { /* 関数本体 */ }
// 中置記法で関数を呼ぶ
1 shl 2
// 次と同じ
1.shl(2)
中置関数呼び出しの優先順位は、算術演算子、型キャスト、
rangeTo演算子より低くなります。次の式はすべて等価です。1 shl 2 + 3は1 shl (2 + 3)と同じ0 until n * 2は0 until (n * 2)と同じxs union ys as Set<*>はxs union (ys as Set<*>)と同じ
一方、中置関数呼び出しの優先順位は、ブール演算子
&&と||、isとinのチェック、ほかのいくつかの演算子より高くなります。次の式も等価です。a && b xor cはa && (b xor c)と同じa xor b in cは(a xor b) in cと同じ
中置関数では、常にレシーバーと引数を明示する必要があります。現在のレシーバーで中置記法でメソッドを呼ぶときは、明示的に
thisを使ってください。これは解決を明確にするためです。
関数のスコープ (Function Scope)
- Kotlin の関数はファイルのトップレベルで宣言できます。つまり、Java、C#、Scala などの言語で必要だったように、関数を保持するクラスを作る必要がありません(Scala 3 以降ではトップレベル定義を利用できます)。トップレベル関数に加え、Kotlin の関数はローカル関数、メンバー関数、拡張関数としても宣言できます。
ローカル関数
- Kotlin は、他の関数内部で定義するローカル関数をサポートしています。
- ローカル関数は、外側の関数のローカル変数(クロージャー)にアクセスできます。上記の例では、
visitedをローカル変数にできます。
メンバー関数
- メンバー関数は、クラスまたはオブジェクト内部で定義される関数です。
class Sample {
fun foo() { print("Foo") }
}
- メンバー関数はドット記法で呼びます。
Sample().foo() // Sample クラスのインスタンスを作成し、foo を呼ぶ
ジェネリック関数
- 関数はジェネリックパラメーターを持てます。型パラメーターは山括弧を使い、関数名の前に指定します。
fun <T> singletonList(item: T): List<T> { /*...*/ }
末尾再帰関数
- Kotlin は、末尾再帰と呼ばれる関数型プログラミングスタイルをサポートしています。ループを使うことが一般的な一部のアルゴリズムでは、スタックオーバーフローのリスクがない再帰関数で代用できます。関数が
tailrec修飾子で-marked され、必要な条件を満たすと、コンパイラーが再帰を最適化し、高速で効率的なループベースの版に置き換えます。
tailrec修飾子の条件を満たすには、関数を呼び出すことが最後に実行する操作でなければなりません。再帰呼び出しより後にコードがある場合、try/catch/finallyブロック 内にある場合、またはopen関数である場合は、末尾再帰を使えません。現在、Kotlin/JVM と Kotlin/Native は末尾再帰をサポートしています。
高階関数と lambda 式
基本概念
- 関数も値:Kotlin では、関数も数値や文字列と同じように、変数に代入したり、データ構造に格納したり、引数として他の関数に渡したり、関数から返したりできます。
- 高階関数:簡単に言えば、関数を受け取るか、関数を返す関数です。
- lambda 式:簡単に言えば、匿名関数で、関数をすばやく定義して使うことができます。
高階関数とは?
- 定義:関数を受け取るか、関数を返す関数です。
例:
fold関数は典型的な高階関数です。fold関数は初期値と結合関数を受け取ります。コレクションのすべての要素を反復処理し、結合関数で現在の要素と累積値を組み合わせ、最終的な結果を返します。コード例(簡略化):
CodeBlock Loading...combine引数は関数型で、2つの引数(R と T)を受け取り、R 型の値を返します。
foldの呼び出し方:lambda 式または関数参照を使います。例:
CodeBlock Loading...
関数型
- 用途:Kotlin は関数を表すために関数型を使います。たとえば
(Int) -> StringはInt引数を受け取りStringを返す関数を表します。 - 構文:
(パラメーター型1, パラメーター型2, ...) -> 戻り値型。()は引数がないことを表します。たとえば() -> Unitです。Unitは java のvoidに相当します。A.(B) -> Cはレシーバーを持つ関数型です。A 型の拡張関数として見ることができます。
- Nullable な関数型:
((Int, Int) -> Int)?は、この関数型がnullになり得ることを表します。 - 型エイリアス:
typealiasで関数型に別名を付けられます。
typealias MyHandler = (String, Int) -> Unit
関数型のインスタンスを作る方法
次の方法があります。
- Lambda 式:
{ a, b -> a + b } - 匿名関数:
fun(s: String): Int { return s.toIntOrNull() ?: 0 } - 関数参照:
::isOdd(トップレベル関数)、String::toInt(メンバー関数) - 関数型インターフェースを実装するクラス:
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO() // invoke メソッドを実装
}
関数型のインスタンスを呼び出す
invoke(...)を使う:f.invoke(x)- 直接呼び出す:
f(x) - 関数型にレシーバーがある場合は、拡張関数と同じように呼べます:
1.foo(2)。
クロージャー
- 定義:lambda 式または匿名関数は、外側のスコープで定義された変数にアクセスできます。
- 外側の変数の変更:lambda 式は、外側のスコープの変数を変更できます。
var sum = 0
numbers.forEach { sum += it }
println(sum) // 出力:numbers の全要素の総和
レシーバーを持つ関数リテラル
- 型:
A.(B) -> C - 用途:lambda 式内で、拡張関数と同じようにレシーバーオブジェクトのメソッドを呼び出せます。
thisキーワード:lambda 式では、thisキーワードでレシーバーオブジェクトを参照できます。- 例:
val sum: Int.(Int) -> Int = { other -> this + other }
println(1.sum(2)) // 出力:3
型安全なビルダー
- レシーバーを持つ lambda 式は、型安全な DSL(ドメイン固有言語)を構築するためによく使われます。
Lambda 式
- 構文:
{ パラメーター -> 関数本体 } - パラメーター型の推論:コンパイラーがパラメーター型を推論できる場合、型を省略できます。
- トレイリング lambda:関数の最後の引数が関数型なら、lambda 式を括弧の外に置けます。
numbers.filter { it > 0 }
itキーワード:lambda 式に引数が1つだけある場合、itキーワードでその引数を参照できます。numbers.map { it * 2 }
- 戻り値:lambda 式の最後の式の値が戻り値になります。
return@labelで明示的に戻すこともできます。 - アンダースコア
_:lambda 式の引数を使わない場合、引数名の代わりにアンダースコア_を使えます。map.forEach { (_, value) -> println(value) }
匿名関数
- 構文:
fun(パラメーター: 型): 戻り値型 { 関数本体 } - 特徴:
- 戻り値型を明示できます。
return文は、匿名関数を含む関数ではなく、匿名関数自身から戻ります(lambda 式とは異なります)。
インライン関数
基本概念
- インライン関数は特殊な関数で、通常の関数のように呼び出すのではなく、コンパイラーが呼び出し箇所に関数本体を直接挿入できるようにします。これにより、関数呼び出しのオーバーヘッドを減らし、特に呼び出しが非常に頻繁な場合にパフォーマンスを向上できます。
なぜインライン関数が必要なのか?
- パフォーマンスの最適化:状況によっては、関数呼び出しのオーバーヘッドが大きくなる場合があります。特にループ内で頻繁に呼ばれる関数では、インライン関数を使うことでこのオーバーヘッドを避け、コードの実行効率を高められます。
- 非局所 return:インライン関数を使うと、lambda 式内で
return文を使って、含む関数から直接戻れます。非常に便利な場合があります。
インライン関数の使い方
- 関数定義の前に
inlineキーワードを付けます。
inline fun <T> lock(lock: Lock, body: () -> T): T { ... }
- これにより、コンパイラーは
lock()関数のコードを、その呼び出し箇所へ直接埋め込もうとします。
インライン関数の制限
- パフォーマンスの問題:インライン関数はパフォーマンスを向上できますが、過度に使うと生成コードのサイズが大きくなります。そのため、大きな関数のインラインは避け、適度に使ってください。
- 非局所 return:インライン関数では、lambda 式から
return文を使って、含む関数から直接戻せます。非常に便利ですが、コードのロジックが複雑になるcause it too。
noinline キーワード
- lambda 式引数の一部だけをインライン wanting場合は、
noinlineキーワードを使います。
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }
inlined引数はインラインされますが、notInlinedはインラインされません。noinlineの lambda 式は通常の関数と同じように、変数に格納したり、別の関数へ渡したりできます。
非局所 return
- 通常の lambda 式では、lambda 式が外側の関数から戻れないため、
returnを直接使うことはできません。 - しかし、lambda 式がインラインされていれば、
returnを使って、lambda 式を含む外側の関数から直接戻せます。これを非局所 returnと呼びます。
fun foo() {
inlined {
return // OK: the lambda is inlined, foo() から直接戻る
}
}
crossinline キーワード
- インライン関数が別の実行コンテキスト(ローカルオブジェクトやネストされた関数など)で lambda 式を呼ぶ場合、その lambda 式では非局所 return を使えません。
crossinlineキーワードでこれを制約できます。
break と continue
- インライン関数では、lambda 式から
breakやcontinueを使って、含むループの制御フローへ影響させられます。
reified キーワード
- Kotlin では、ジェネリックは実行時に消去されます。そのため、関数内部から具体的なジェネリック型へアクセスできません。
reifiedキーワードを使うと、通常クラスと同じように、インライン関数内でジェネリック型へアクセスできます。例:
inline fun <reified T> TreeNode.findParentOfType(): T? { ... }
- これにより|reflection を使わずに
T::classやT is Typeなどの操作ができます。 - 注意:
reifiedキーワードを使えるのはインライン関数だけです。
インラインプロパティ
inlineキーワードはプロパティのget()とset()メソッドにも使えます。プロパティ全体をインラインするか、getter と setter を個別にインラインできます。
public API 内のインライン関数の制限
- インライン関数が public API の一部(
publicまたはprotected)である場合、他のモジュールから呼ばれ、そこでもインラインされます。 - バイナリ互換性の問題を避けるため、public API のインライン関数では public 以外の API(
privateまたはinternal)を使えません。 @PublishedApi注釈を使うと、public API のインライン関数でinternal宣言を使えるようになります。
演算子オーバーロード
基本概念
- 演算子オーバーロード allows you to define operator behavior for custom types. これにより、標準演算子(
+、-、*、/など)を使って,基本型と同じようにカスタム型を操作できます。
演算子オーバーロードの実装方法
- Kotlin では、演算子オーバーロードはクラス内で特定の関数を定義して実装します。これらの関数には
operatorキーワードを付けます。
単項演算子
| 演算子 | 対応する関数 | 例 | 説明 |
|---|---|---|---|
+ | unaryPlus() | +a | プラス符号(通常は何も変更しない) |
- | unaryMinus() | -a | 符号を反転 |
! | not() | !a | 論理否定(反転) |
例
インクリメント/デクリメント演算子 (++, --)
a++(後置インクリメント):まずaの元の値を返し、その後aをインクリメントします。++a(前置インクリメント):まずaをインクリメントし、その後新しい値を返します。a--と--aも同じ规律です。重要:
inc()とdec()関数は新しい値を返し、変数に代入する必要があります。元のオブジェクトを直接変更しないでください。
二項演算子
| 演算子 | 対応する関数 | 例 | 説明 |
|---|---|---|---|
+ | plus(b) | a + b | 加法 |
- | minus(b) | a - b | 減算 |
* | times(b) | a * b | 乗算 |
/ | div(b) | a / b | 除算 |
% | rem(b) | a % b | 余り(剰余) |
.. | rangeTo(b) | a..b | 範囲を作成(たとえば 1..10) |
..< | rangeUntil(b) | a..<b | 範囲を作成(最後の要素を含まない) |
例
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}
in 演算子
a in b:aがbに含まれるか確認します。対応する関数:b.contains(a)a !in b:aがbに含まれないか確認します。対応する関数:!b.contains(a)
インデックスアクセス演算子 ([])
a[i]:配列やコレクションの要素 accesses。对応する関数:a.get(i)a[i] = b:配列やコレクションの要素の値を設定します。対応する関数:a.set(i, b)
invoke 演算子 (())
a():関数のようにオブジェクトを呼び出します。対応する関数:a.invoke()
複合代入演算子 (+=, -=, *=, /=, %=)
a += b:plusAssign関数がない場合、a = a + bと同じです。a.plusAssign(b)を優先します。- 他の演算子も同様です。
等価・非等価演算子 (==, !=)
a == b:aとbが等しいか確認します。対応する関数:a?.equals(b) ?: (b === null)a != b:aとbが等しくないか確認します。対応する関数:!(a?.equals(b) ?: (b === null))
比較演算子 (>, <, >=, <=)
a > b:aとbの大小を比較します。対応する関数:a.compareTo(b) > 0- 他の演算子も同様です。
注意
===と!==(同じオブジェクトかどうかを確認)はオーバーロードできません。
this 式
基本概念
thisは「現在のオブジェクト」を指します。别人に話しながら「私」で自分を指すのと同じように考えることができます。
this の使い方
- クラスの中:
thisは現在のクラスのオブジェクトを指します。たとえばPersonクラスでは、thisは現在のPersonオブジェクトです。 - 拡張関数やレシーバーを持つ関数リテラルの中:
thisはレシーバーパラメーター、つまりドット (.) の左側にあるオブジェクトを指します。
this の参照先
this が1つだけの場合、通常は最も内側のスコープを指します。ただし、外側のスコープの this へアクセスするには、限定付きの this を使います。
限定付きの this (Qualified this)
外側のスコープ(外部クラス、拡張関数、ラベルの付いた関数リテラルなど)から this へアクセスしたい場合は、this@label 形式を使えます。
@labelはスコープのラベルです。
例
暗黙の this (Implicit this)
this オブジェクトのメンバー関数を呼び出す場合、this. の部分を省略できます。
注意
同じ名前の非メンバー関数がある場合は this の用法に注意してください。状況によっては、非メンバー関数が呼ばれる可能性があります。
例
注釈 (Annotations)
基本概念
- 注釈は、コードに付加情報を与える方法で、コードへラベルを贴るのに似ています。この情報は、コンパイラー、ツール、実行環境から利用できます。
注釈の宣言方法
classキーワードの前にannotationキーワードを付けます。
annotation class Fancy
注釈のメタ注釈 (Meta-annotations)
メタ注釈は注釈を修飾する注釈で、注釈の動作を指定するために使います。
@Target:注釈をどのコード要素へ使えるか指定します(クラス、関数、プロパティなど)。@Retention:注釈をコンパイル後の class ファイルに格納するか、実行時にリフレクションでアクセスできるかを指定します。既定では両方ともtrueです。@Repeatable:同じコード要素で同じ注釈を複数回使えるようにします。@MustBeDocumented:注釈が public API の一部であり、生成される API ドキュメントへ含める必要があることを指定します。
例
注釈の使い方
@Fancy class Foo {
@Fancy fun baz(@Fancy foo: Int): Int {
return (@Fancy 1)
}
}
注釈コンストラクター
- 注釈には、引数を取るコンストラクターを持たせられます。
annotation class Special(val why: String)
@Special("example") class Foo {}
使用できる引数型
- Java プリミティブ型(Int、Long など)
- 文字列(String)
- クラス(Foo::class)
- 列挙(Enum)
- その他の注釈
- 上記の型の配列
注意
- 注釈の引数には nullable 型を使えません。JVM は null を注釈のプロパティ値としてサポートしていないためです。
注釈を引数にする
- 注釈を別の注釈の引数として使う場合、その名前に
@を付ける必要はありません。
クラスを引数にする
- クラスを注釈の引数にする必要がある場合は、Kotlin の
KClassを使います。Kotlin コンパイラーは自動的に Java クラスへ変換するため、Java コードから注釈と引数へアクセスできます。
import kotlin.reflect.KClass
annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any>)
@Ann(String::class, Int::class) class MyClass
注釈クラスのインスタンス化
- Kotlin では、注釈クラスのコンストラクターを通常クラスと同じように呼び出し、生成されたインスタンスを使えます。
Lambda 式への注釈
- 注釈は lambda 式に使えます。注釈は lambda 式が生成する
invoke()メソッドへ適用されます。
annotation class Suspendable
val f = @Suspendable { Fiber.sleep(10) }
注釈の使用位置 (Use-site Targets)
- プロパティや主コンストラクター引数へ注釈を付けると、複数の Java 要素が生成されます。注釈を別の位置に配置できます。使用位置ターゲットを使うと、注釈をどの Java 要素へ生成するかを正確に指定できます。
よく使われる使用位置ターゲット
file:ファイル全体property:プロパティ(Java からは見えない)field:フィールドget:プロパティの getterset:プロパティの setterreceiver:拡張関数やプロパティのレシーバーパラメーターparam:コンストラクター引数setparam:プロパティ setter の引数delegate:委譲プロパティの委譲インスタンスフィールド
同じターゲットを持つ複数の注釈
- 同じ使用位置ターゲットを持つ注釈が複数ある場合、ターゲットを繰り返し指定しないように、角括弧で囲めます。
class Example {
@set:[Inject VisibleForTesting]
var collaborator: Collaborator
}
Java 注釈との互換性
- Kotlin と Java の注釈は100%互換です。
Java 注釈の引数
- Java 注釈の引数順は定義されていないため、通常の関数呼び出し構文で引数を渡すことはできません。名前付き引数構文を使う必要があります。
// Java
public @interface Ann {
int intValue();
String stringValue();
}
// Kotlin
@Ann(intValue = 1, stringValue = "abc") class C
value 引数の特殊な場合
- Java で注釈に
valueという名前の引数が1つだけある場合、Kotlin では引数名を省略できます。
// Java
public @interface AnnWithValue {
String value();
}
// Kotlin
@AnnWithValue("abc") class C
配列を注釈の引数にする
- Java で注釈の引数型が配列の場合、Kotlin では
vararg引数になります。
// Java
public @interface AnnWithArrayValue {
String[] value();
}
// Kotlin
@AnnWithArrayValue("abc", "foo", "bar") class C
- その他の配列型の引数には、配列リテラル構文または
arrayOf(...)が必要です。
// Java
public @interface AnnWithArrayMethod {
String[] names();
}
@AnnWithArrayMethod(names = ["abc", "foo", "bar"])
class C
注釈インスタンスのプロパティにアクセス
- 注釈インスタンスの値は、Kotlin ではプロパティとして公開されます。
// Java
public @interface Ann {
int value();
}
// Kotlin
fun foo(ann: Ann) {
val i = ann.value // value プロパティへ直接アクセス
}
繰り返し使用できる注釈 (Repeatable Annotations)
- Kotlin は、同じコード要素で同じ注釈を複数回使える、繰り返し使用できる注釈をサポートしています。注釈宣言に
@kotlin.annotation.Repeatableメタ注釈を付けると、Kotlin と Java の両方で繰り返し使用できます。
@Repeatable
annotation class Tag(val name: String)
// コンパイラーが @Tag.Container を含む注釈を自動生成する
@kotlin.jvm.JvmRepeatableメタ注釈を使い、含める注釈クラスを明示的に宣言して引数として渡すと、含める注釈にカスタム名を設定できます。
@JvmRepeatable(Tags::class)
annotation class Tag(val name: String)
annotation class Tags(val value: Array<Tag>)
- Kotlin または Java の繰り返し使用できる注釈をリフレクションで取り出すには、
KAnnotatedElement.findAnnotations()関数を使います。
分解
基本概念
- 分解宣言は、オブジェクトを複数の変数へ分解する、便利な構文です。
構文
仕組み:componentN() 関数
- Kotlin は内部で、一連の
componentN()関数を呼んで分解を実現します。 component1()は1番目の変数に対応します。component2()は2番目の変数に対応します。同様に
component3()、component4()などが続きます。したがって、
componentN()関数を持つオブジェクトなら、分解宣言を使えます。
operator キーワード
componentN()関数を分解宣言で使うには、operatorキーワードを付ける必要があります。
ユースケース
forループ:コレクションから要素を便捷に extracts。
for ((a, b) in collection) {
// a と b は、それぞれコレクションの各要素の component1() と component2() の戻り値
}
- 関数から複数の値を返す:
data classを使うと、複数値を方便的かつ直接分解して返せます。
- Map の走査:
forループ内で Map のキーと値の組を直接分解できます。
不要な変数を無視する:アンダースコア _
- 分解時に必要のない変数には、変数名の代わりにアンダースコア
_を使えます。Kotlin は対応するcomponentN()関数を呼ばないため、リソースを節約できます。
val (_, status) = getResult() // status だけが必要なので、最初の戻り値は無視
Lambda 式内の分解
- lambda 式の引数で分解宣言を使うと、コードを簡潔にできます。
Lambda 内の型宣言
- 分解引数全体、または個々のコンポーネントに型を指定できます。
クラス参照、関数参照、プロパティ参照、コンストラクター参照、バインド済み参照
Kotlin は複数の参照タイプを提供し、プログラムの構造を実行時に操作できます。より柔軟で強力なコードを書くうえで、これらの参照タイプを理解することは重要です。
クラス参照 (Class References): ::class
- 用途:Kotlin クラスの実行時
KClassオブジェクトを取得します。 - 構文:
ClassName::class - 型:
KClass<ClassName> - 用途:
- 型チェック(
is演算子) - クラスの情報の取得(名前、コンストラクター、プロパティなど)
- Java リフレクションとの相互運用(
.javaプロパティでjava.lang.Classオブジェクトを取得)
- 型チェック(
- 例:
関数参照 (Function References): ::
- 用途:関数の参照を取得し、関数型の値のように使えます。
- 構文:
::functionName - 型:
(ParameterTypes) -> ReturnTypeまたはKFunction<out R> - 用途:
- 関数を高階関数(
filter、map、reduceなど)の引数として渡す - 関数組み合わせを作成する
- 関数を動的に呼び出す
- 関数を高階関数(
- 例:
プロパティ参照 (Property References): ::
- 用途:プロパティの参照を取得し、プロパティの値とメタデータへアクセスできます。
- 構文:
::propertyName - 型:
KProperty0<Type>(トップレベルプロパティまたはレシーバーのないプロパティ)KProperty1<Receiver, Type>(レシーバーを持つプロパティ)KMutableProperty...(可変プロパティ)
- 用途:
- プロパティ値の動的な取得と設定
- プロパティの名前と型へのアクセス
- 高階関数での利用(
mapでオブジェクトのプロパティ値を抽出,等等)
- 例:
コンストラクター参照 (Constructor References): ::ClassName
- 用途:コンストラクターの参照を取得し、クラスのインスタンスを動的に作成できます。
- 構文:
::ClassName - 型:
(ConstructorParameterTypes) -> ClassNameまたはKFunction<out ClassName> - 用途:
- 依存性注入
- オブジェクトの動的な作成
- ファクトリーパターン
- 例:
バインド済み参照 (Bound References): instance::member
- 用途:関数やプロパティを特定のオブジェクトインスタンスへバインドします。
- 構文:
instance::memberName(memberNameは関数でもプロパティでもよい) - 型:バインドしたメンバーとインスタンスに合わせて調整され、レシーバーパラメーターを含まなくなります。
- 用途:
- オブジェクトインスタンスを毎回渡す必要をなくし、コードを簡潔にする
- 状態 Related の関数参照を作成する
- 関数型プログラミングでオブジェクトの特定の動作を渡す
- 例:
主な違いのまとめ
| 特性 | クラス参照 (::class) | 関数参照 (::) | プロパティ参照 (::) | コンストラクター参照 (::ClassName) | バインド済み参照 (instance::member) |
|---|---|---|---|---|---|
| 参照対象 | クラス | 関数 | プロパティ | コンストラクター | インスタンス + メンバー(関数またはプロパティ) |
| 主な用途 | 型情報、相互運用 | 関数型プログラミング | 動的アクセス | オブジェクトの動的作成 | コードの簡略化、状態依存 |
| バインド | バインドしない | バインドしない | バインドしない | バインドしない | 特定のオブジェクトインスタンスへバインド |
適切な参照タイプの選び方
- クラス自体の情報を操作する必要がある場合は、クラス参照 (
::class) を使います。 - 関数を引数として渡したり、動的に呼び出したりする必要がある場合は、関数参照 (
::functionName) を使います。 - プロパティ値を動的にアクセスしたり、プロパティのメタデータを取得したりする必要がある場合は、プロパティ参照 (
::propertyName) を使います。 - クラスのインスタンスを動的に作成する必要がある場合は、コンストラクター参照 (
::ClassName) を使います。 - 関数やプロパティを特定のオブジェクトインスタンスへバインドする必要がある場合は、バインド済み参照 (
instance::member) を使います。