Kotlin in Action読書メモ
第2版
1 Kotlin What and why
2 Kotlin basics
-
2.1 基本要素: 関数と変数: このセクションでは、Kotlinプログラムの基本的な構成要素である関数と変数について紹介します。
-
関数:
-
関数は
fun
キーワードを使って宣言します。クラスに含める必要はなく、ファイルのトップレベルに定義できます。 -
main
関数はアプリケーションのエントリーポイントとして機能し、引数なしでトップレベルに宣言することができます。 -
Kotlinの関数にはパラメータと戻り値があります。パラメータは
name: Type
という構文で定義し、戻り値の型はパラメータリストの後に指定します。例えば:fun max(a: Int, b: Int): Int { return if (a > b) a else b }
-
Kotlinの
if
文は式であるため、他の式で利用できる値を持ちます。このため、上記のmax
関数のように簡潔な関数定義が可能です。
-
-
変数:
-
変数は読み取り専用の参照(不変)には
val
キーワードを、変更可能な変数にはvar
キーワードを使って宣言します。Kotlinは可能な限りval
を使用して、不変性や関数型プログラミングの実践を推奨しています。 -
Kotlinは型推論をサポートしており、コンテキストから型が推論できる場合は型宣言を省略できます。例えば:
val question = "生命、宇宙、そして万物についての究極の質問" val answer = 42
-
文字列テンプレート を使用することで、文字列のフォーマットが簡単になります。変数や式を
$
プレフィックスまたは${}
を使って直接文字列に埋め込むことができます。
-
-
-
2.2 振る舞いとデータのカプセル化: クラスとプロパティ:
-
クラス:
-
Kotlinは特にデータクラス(主にデータを保持するクラス)の宣言において、簡潔な構文を提供します。データクラスには特別な構文があり、Chapter 1で説明された
Person
クラスの例のように宣言が簡単になります。この構文はより冗長なJavaコードを置き換えることができます。例えば、次のKotlinコード:class Person(val name: String)
は、次のJavaコードと同等です:
public class Person { private final String name; public Person(String name) { this.name = name; } public String getName() { return name; } }
-
Kotlinのクラスはデフォルトでfinalかつpublicです。
-
Kotlinのクラスには静的メンバーを持つことができません。代わりに、静的ユーティリティにはパッケージレベルの関数を使用し、必要に応じてオブジェクト宣言で静的メソッドやフィールドを実現します。
-
-
プロパティ:
- プロパティはKotlinにおけるデータとそのアクセサ(getterおよびsetter)をカプセル化する第一級の言語機能です。
-
val
は読み取り専用プロパティ、var
は変更可能なプロパティとして宣言します。 - カスタムのgetterおよびsetterを提供することで、プロパティのアクセス方法や変更方法を制御できます。
-
-
2.3 選択肢の表現と処理: 列挙型とwhen:
-
列挙型:
-
列挙型は
enum class
キーワードを使って宣言します。各列挙定数には関連するプロパティやメソッドを持たせることができます。enum class Color(val r: Int, val g: Int, val b: Int) { RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255), INDIGO(75, 0, 130), VIOLET(238, 130, 238); fun rgb() = (r * 256 + g) * 256 + b }
-
-
when式:
-
when
式はJavaのswitch
文の強力な代替手段です。式を評価し、それをさまざまな分岐(列挙定数、範囲、型など)にマッチさせるために使用できます。例えば:fun getMnemonic(color: Color) = when (color) { Color.RED -> "Richard" Color.ORANGE -> "Of" Color.YELLOW -> "York" Color.GREEN -> "Gave" Color.BLUE -> "Battle" Color.INDIGO -> "In" Color.VIOLET -> "Vain" }
-
Kotlinの
when
は式であるため、値を返すことができます。このため、変数に値を代入したり、関数から値を直接返したりするのに使用できます。
-
-
-
2.4 繰り返し処理: whileとforループ:
-
whileループ: Kotlinの
while
ループはJavaと同様に動作し、指定された条件が真である限り、コードブロックを繰り返し実行します。また、ループの実行を制御するためにbreak
およびcontinue
キーワードをサポートしています。 -
forループ: Kotlinの
for
ループは主にコレクションや範囲を反復するために使用され、Javaの拡張forループと似ています。繰り返し要素を指定するためにin
キーワードを使用します。-
範囲: Kotlinでは
..
演算子を使って範囲を簡潔に作成できます。例えば、1..10
は1から10までの数字を表します。Fizz-Buzzの例に示されるように、for
ループを使用して範囲を繰り返し処理することができます。
-
範囲: Kotlinでは
-
-
2.5 Kotlinでの例外のスローとキャッチ:
-
例外処理: Kotlinの例外処理はJavaと同様に、
try
、catch
、finally
ブロックを使用します。 - チェック例外がない: Javaとは異なり、Kotlinにはチェック例外がありません。関数がスローする可能性のある例外を宣言する必要がないため、例外処理が簡素化され、どの例外を明示的に処理するかを選択できます。
-
try式: Kotlinの
try
ブロックは式であり、値を返すことができます。これにより、変数に値を代入したり、関数から値を直接返したりするのに使用できます。
-
例外処理: Kotlinの例外処理はJavaと同様に、
3 Defining and calling functions
第3章では、Kotlinが関数を定義、呼び出し、管理しやすくするためにさまざまな機能を導入していることを強調しています。拡張関数やプロパティなどの機能を追加することで、Javaで書かれたコードを含む既存のコードベースをクリーンかつシームレスに拡張することができます。また、Kotlinはコレクションや文字列を操作するための強力で簡潔なツールを提供しています。
-
3.1 Kotlinでのコレクションの作成: Kotlinは既存のJavaコレクションクラス(
java.util.Collection
)を利用していますが、これらのコレクションの作成と操作を強化しています。KotlinはsetOf
、listOf
、mapOf
などの簡潔な関数を提供して、不変のコレクションを作成できます。例えば、val list = listOf(1, 7, 53)
で数値のリストを作成できます。可変コレクションの場合、mutableListOf
、mutableSetOf
、mutableMapOf
のような関数を使用します。KotlinはJavaコレクションの機能を拡張して、多数の拡張関数を追加し、コレクションの操作をより便利にしています。これらの拡張関数には、リストの最後の要素を取得する
last
、要素をシャッフルするshuffled
、要素の合計を計算するsum
などのメソッドが含まれます。例えば:fun main() { val strings = listOf("first", "second", "fourteenth") strings.last() // "fourteenth" println(strings.shuffled()) // リストのランダムな順列 val numbers = setOf(1, 14, 2) println(numbers.sum()) // 17 }
標準のJavaコレクションを使用することで、Javaコードと相互運用する際に変換が不要になります。
-
3.2 関数の呼び出しを簡単にする: Kotlinはいくつかの機能を導入して、関数の呼び出しを簡略化し、より使いやすくしています。
-
名前付き引数: Kotlinでは、関数に渡す引数に名前を付けることができ、コードの可読性が向上します。特に多くのパラメータを持つ関数を扱う場合に便利です。Javaでは各引数の目的を明確にするためにコメントを使用することが一般的ですが、Kotlinの名前付き引数はコードをより自己説明的にします。例えば、
joinToString(collection, separator, prefix, postfix)
という関数で名前付き引数を使うと、呼び出しがより理解しやすくなります:joinToString(collection, separator = "; ", prefix = "(", postfix = ")")
名前付き引数を使用することで、関数呼び出し時の引数の順序を変更する柔軟性も得られます。
-
デフォルトのパラメータ値: Kotlinでは関数パラメータにデフォルト値を指定できるため、異なる引数の組み合わせを処理するために複数のオーバーロード関数を作成する必要がなくなります。例えば、
joinToString
関数では、separator
、prefix
、postfix
パラメータにデフォルト値を提供することで、一般的なケースでの呼び出しを簡略化できます。次の例ではデフォルトパラメータを定義しています:fun <T> joinToString( collection: Collection<T>, separator: String = ", ", // デフォルトの区切り文字 prefix: String = "", // デフォルトのプレフィックス postfix: String = "" // デフォルトのポストフィックス ): String { /*...*/ }
この関数を呼び出す際には、デフォルト値を持つパラメータを省略することができます。
-
トップレベル関数とプロパティ: Kotlinでは、すべての関数をクラスのメンバーにする必要がありません。関数やプロパティをソースファイルのトップレベルに直接定義することができます。これにより、Javaで静的メソッドのコンテナとして使用されるユーティリティクラスを作成する必要がなくなります。例えば、文字列操作のためのトップレベル関数を含む
StringUtil.kt
という名前のファイルを持つことができます:package strings fun joinToString(/* ... */): String { /* ... */ }
これらの関数は他のファイルからインポートしてアクセスできます。Javaコードからは、これらのトップレベル関数はファイルにちなんだクラスの静的メソッドとしてアクセスできます(例:
StringUtilKt.joinToString
)。
-
-
3.3 他人のクラスにメソッドを追加する: 拡張関数とプロパティ: Kotlinは、既存のクラスに新しい関数やプロパティを追加する拡張という強力なメカニズムを導入しています。これにより、外部ライブラリやフレームワークのクラスの機能を強化し、よりKotlinらしい使い方ができるようになります。
-
拡張関数: 拡張関数は
fun ReceiverType.functionName(parameters) { ... }
という構文で定義されます。ReceiverType
は関数が追加されるオブジェクトの型を指定します。例えば、String
クラスにlastChar
という関数を追加して、文字列の最後の文字を取得できるようにします:fun String.lastChar(): Char { return this.get(this.length -1) }
その後、任意の
String
インスタンスでこの関数をメンバー関数のように呼び出すことができます:val c = "Kotlin".lastChar()
-
拡張プロパティ: 拡張プロパティも、拡張関数と同様に既存のクラスに新しいプロパティを追加する方法を提供します。これらのプロパティはカスタムアクセサ(
get
およびset
)を使用して実装されます。ただし、拡張プロパティは元のクラスに新しいフィールドを追加できないため、状態を保存することはありません。例えば、StringBuilder
に対してlastChar
という拡張プロパティを定義し、最後の文字にアクセスしたり変更したりする便利な方法を提供します:val StringBuilder.lastChar: Char get() = get(length - 1) set(value: Char) { this.setCharAt(length - 1, value) }
-
拡張関数のオーバーライドは不可: 拡張関数はコンパイル時に静的に解決されます。もしクラスが同じシグネチャのメンバー関数を既に持っている場合、メンバー関数が常に優先されます。このため、拡張関数は既存のメンバー関数をオーバーライドすることはできません。
-
-
3.4 コレクションの操作: 可変長引数、インフィックス呼び出し、およびライブラリサポート: Kotlinはコレクションの操作を強化するための機能を提供しています。
-
可変長引数(Varargs): Kotlinの
vararg
キーワードを使用すると、任意の数の引数を受け取る関数を定義できます。これらの引数は配列として関数に渡されます。例えば、listOf
関数はvararg
を使用して任意の数の要素を持つリストを作成します:fun listOf<T>(vararg values: T): List<T> { /* ... */ }
この関数は複数の引数で呼び出せます:
val list = listOf(2, 3, 5, 7, 11)
。 -
インフィックス呼び出し: Kotlinでは、特定の関数を1つの引数でインフィックス表記で呼び出すことができます。これにより、コードの可読性が向上し、より自然な言語のような構造を作り出します。インフィックス表記を有効にするには、関数を
infix
修飾子でマークします。一般的な例として、ペアを作成するためのto
関数があります:infix fun Any.to(other: Any) = Pair(this, other)
インフィックス表記で呼び出すことができます:
1 to "one"
。 -
分割宣言(Destructuring Declarations): Kotlinは分割宣言を提供しており、単一の複合値を複数の変数に分割する便利な機能です。これは特に、データクラスや複数の値を返す関数と連携するときに有用です。例えば、
Pair
を分割することができます:val (number, name) = 1 to "one"
これにより、
1
がnumber
に、"one"
がname
に割り当てられます。
-
-
3.5 文字列と正規表現の操作: Kotlinは文字列と正規表現の操作を簡単にするために、さまざまな便利な関数や機能を提供しています。
-
文字列の分割: KotlinはJavaの
split
メソッドに関する一般的な問題を解決し、文字列の分割に便利な関数を提供しています。Kotlinのsplit
関数は正規表現パターンを扱い、より単純なケース向けのオーバーロードも提供しています。 -
トリプルクオート文字列: Kotlinはトリプルクオート文字列を導入しており、複数行の文字列やJavaでエスケープが必要な表現を簡潔に記述する方法を提供しています。これにより、特殊文字を含む文字列や複数行にわたる文字列の作成が簡素化されます。例えば:
val path = """c:\users\admin\Documents\File.txt"""
-
-
3.6 コードを整理する: ローカル関数と拡張: Kotlinはローカル関数と拡張を使用してコードの整理と重複の排除を推奨しています。
-
ローカル関数: Kotlinでは、関数の内部に別の関数を定義することができ、これをローカル関数と呼びます。これによりコードの構造化が促進され、関数内で繰り返されるコードブロックを排除することができます。ローカル関数はその外側のスコープの変数にアクセスできるため、コードを構造化するのに有用です。
-
拡張: 先述のとおり、拡張(関数とプロパティ)は既存のクラスに機能を追加する強力なメカニズムを提供し、ソースコードを変更せずに既存のクラスを拡張できます。これにより、コードの再利用が促進され、コードの可読性と保守性が向上します。
-
-
Kotlinは、Javaコレクション・クラスをよりリッチなAPIで強化している。
-
関数のパラメータにデフォルト値を定義することで、オーバーロード関数を定義する必要性が大幅に減り、名前付き引数構文により、多くのパラメータを持つ関数の呼び出しがより読みやすくなる。
-
関数やプロパティは、クラスのメンバとしてだけでなく、ファイル内で直接宣言できるため、より柔軟なコード構造が可能になります。
-
拡張関数と拡張プロパティを使用すると、外部ライブラリで定義されたクラスを含め、任意のクラスの API をソース・コードを変更することなく、実行時のオーバーヘッドなしに拡張できます。
-
Infix呼び出しは、単一の引数で演算子のようなメソッドを呼び出すためのクリーンな構文を提供します。
-
Kotlinは、正規表現とプレーンな文字列の両方に対して、便利な文字列処理関数を多数提供しています。
-
三重引用符で囲まれた文字列は、Javaではノイズの多いエスケープや文字列連結を必要とする式をすっきりと書く方法を提供します。
-
ローカル関数は、コードをよりきれいに構成し、重複をなくすのに役立つ。
4 Classes, objects, and interfaces
第4章では、Kotlinがクラス、オブジェクト、およびインターフェースを定義し操作するためのアプローチを包括的に紹介しています。シールドクラス、データクラス、クラス委譲、インラインクラスといった機能を提供することで、Kotlinは開発者がボイラープレートを最小限に抑えながら表現力豊かで安全なコードを書くことを可能にします。object
キーワードはその多様な役割により、シングルトン、コンパニオンオブジェクト、匿名オブジェクトを実装するための便利な方法を提供し、柔軟性をさらに高めています。
Kotlinのクラス、オブジェクト、インターフェースに対するアプローチ: コード例を用いた詳細な探求
-
4.1 クラス階層の定義: このセクションでは、Kotlinがクラスとインターフェースの関係をどのように構築するかを探ります。継承、抽象クラス、可視性修飾子などの概念を含みます。
-
インターフェース: Kotlinのインターフェースは、Javaのインターフェースと同様に、クラスが実装すべき契約を定義します。インターフェースには初期化子のないプロパティの宣言を含めることができ、インターフェースを実装するクラスはプロパティの値を取得する方法を提供する必要があります。例えば:
interface User { val nickname: String } class PrivateUser(override val nickname: String): User // 'nickname'をオーバーライドする必要あり
Kotlinでは、クラスがコロン (
:
) を使用してインターフェースの実装やクラスの継承を示します。Javaの任意の@Override
アノテーションとは異なり、Kotlinではスーパークラスまたはインターフェースからのメンバーをオーバーライドする場合にoverride
修飾子を必ず指定する必要があります。これにより、誤ってオーバーライドすることを防ぎ、コードの明確性を高めます。 -
Open、Final、およびAbstract修飾子: Javaではクラスがデフォルトで継承可能ですが、Kotlinのクラスはデフォルトで
final
であり、明示的にopen
とマークされない限りサブクラス化できません。このアプローチは「脆弱な基底クラス問題」に対処し、基底クラスの変更がサブクラスを意図せず壊してしまうことを防ぎます。以下の例では、RichButton
クラスが拡張可能であり、animate
関数はサブクラスでオーバーライド可能とするためにopen
と明示的にマークされています。open class RichButton: Clickable { fun disable() { /* ... */ } open fun animate() { /* ... */ } override fun click() { /* ... */ } }
abstract
キーワードで宣言された抽象クラスはインスタンス化できず、通常は実装のない抽象メンバーを含み、それらはサブクラスでオーバーライドされる必要があります。例えば、以下のAnimated
クラスは抽象クラスであり、抽象メンバーと非抽象メンバーの両方を含んでいます。abstract class Animated { abstract val animationSpeed: Double val keyframes: Int = 20 open val frames: Int = 60 fun startAnimation() { /* ... */ } open fun stopAnimation() { /* ... */ } }
-
可視性修飾子: Kotlinは4つの可視性修飾子を提供しており、宣言へのアクセスを制御します。
public
(デフォルト)、internal
、protected
、およびprivate
です。public
宣言はどこからでもアクセス可能であり、internal
宣言はモジュール内でのみ可視です。protected
宣言はクラスとそのサブクラスで制限され、private
宣言はクラスメンバーの場合はクラス内、トップレベル宣言の場合はファイル内に限定されます。 -
インナーおよびネストされたクラス: Kotlinでは、ネストされたクラスとインナークラスは異なります。ネストされたクラスは、別のクラスの内部に宣言されたクラスであり、外部クラスのインスタンスにアクセスできません。
inner
修飾子を使って明示的にインナークラスとして宣言する必要があります。一方、インナークラスはJavaのインナークラスと同様に、外部クラスのインスタンスへの参照を保持します。インナークラスから外部クラスのインスタンスにアクセスするには、this@Outer
を使用します。 -
シールドクラス(Sealed Classes): シールドクラスは制限されたクラス階層を作成するメカニズムを提供し、すべての直接的なサブクラスは同じファイル内で宣言され、コンパイル時に知られている必要があります。これにより、シールドクラスの型を扱う
when
式が網羅的になります。例えば、以下のExpr
クラスはシールドクラスであり、その直接的なサブクラスNum
とSum
は同じファイル内に定義されています。sealed class Expr { class Num(val value: Int): Expr() class Sum(val left: Expr, val right: Expr): Expr() }
-
-
4.2 非単純なコンストラクタやプロパティを持つクラスの宣言: Kotlinはプライマリコンストラクタとセカンダリコンストラクタを通じてクラスの初期化に柔軟性を提供し、カスタムプロパティアクセサーロジックを許可します。
-
プライマリコンストラクタと初期化ブロック: プライマリコンストラクタはクラス本体の外に括弧で宣言され、クラスのプロパティを定義し初期化する簡潔な方法を提供します。パラメータにはデフォルト値を含めることができ、クラス外部からのインスタンス化を制限するために
private
にすることもできます。init
でマークされた初期化ブロックはオブジェクトの構築中に追加の初期化ロジックを実行します。以下の例では、User
クラスがプライマリコンストラクタの構文、デフォルトパラメータ値、および初期化ブロックを示しています。class User(val nickname: String, val isSubscribed: Boolean = true) { init { println("User $nickname created!") } }
-
セカンダリコンストラクタ: セカンダリコンストラクタはクラス本体内で
constructor
キーワードを使って定義され、クラスの初期化の別の方法を提供します。セカンダリコンストラクタはthis()
を使ってプライマリコンストラクタ、または他のセカンダリコンストラクタに委任しなければならず、最終的にプライマリコンストラクタまたはスーパークラスのコンストラクタにチェーンされます。セカンダリコンストラクタはJavaとの相互運用性や、クラスのインスタンスを作成する複数の方法が必要な場合に便利です。例えば、以下のMyDownloader
クラスはセカンダリコンストラクタを使い、互いに委任し、スーパークラスのコンストラクタに委任する例を示しています。class MyDownloader : Downloader { constructor(url: String?) : this(URI(url)) constructor(uri: URI?) : super(uri) }
-
インターフェースで宣言されたプロパティの実装: 抽象プロパティの宣言を含むインターフェースを実装するクラスは、これらのプロパティに具体的な実装を提供する必要があります。プロパティの値をバックフィールドに格納するか、プロパティにアクセスされたときに計算するかを選択できます。
-
ゲッターおよびセッターからのバックフィールドへのアクセス: カスタムプロパティアクセサ(ゲッターおよびセッター)では、Kotlinは
field
識別子を使用してバックフィールドに直接アクセスすることができます。これにより、プロパティのアクセスおよび変更ロジックを細かく制御できます。
-
-
4.3 コンパイラ生成メソッド: データクラスとクラス委譲: Kotlinコンパイラは一般的に使用されるメソッドの自動生成を行い、データ中心のクラスのボイラープレートコードを減らし、クラス委譲を簡素化します。
-
ユニバーサルオブジェクトメソッド: すべてのKotlinクラスは
Any
クラスからtoString
、equals
、およびhashCode
を継承しており、これはJavaのjava.lang.Object
から継承するのと同様です。Kotlinはこれらのメソッドの実装を自動生成できますが、特にデータクラスに対して有効です。 -
データクラス:
data
キーワードでマークされたデータクラスはデータを保持するために設計されており、toString
、equals
、hashCode
、およびcopy
といった自動生成されたメソッドを備えています。これらの生成された実装はプライマリコンストラクタで宣言されたすべてのプロパティを考慮します。データクラスはval
プロパティの使用を推奨することで不変性を促進します。例えば、以下のCustomer
データクラスは簡潔な構文と標準メソッドの自動生成を示しています。data class Customer(val name: String, val postalCode: Int)
-
クラス委譲: Kotlinは
by
キーワードを使用してデコレーターパターンをサポートしています。委譲オブジェクトに対して手動でフォワードメソッドを書く代わりに、メソッドの実装を簡潔に委譲できます。このアプローチは冗長なコードを避け、委譲関係を明示的にします。
-
-
4.4 Objectキーワード: クラスの宣言とインスタンスの生成の組み合わせ: Kotlinの
object
キーワードには複数の役割があり、シングルトンの定義、コンパニオンオブジェクトの作成、およびオブジェクト式の実装に使われます。-
オブジェクト宣言: オブジェクト宣言はクラスを定義し、そのクラスの単一のインスタンス(シングルトン)を同時に作成します。コンストラクタを持つことはできません。インスタンスは定義時にすぐに作成されるためです。オブジェクトはクラスやインターフェースを継承することができます。以下の例では、
Payroll
オブジェクト宣言がシングルトンオブジェクトを定義しています。object Payroll { val allEmployees = mutableListOf<Person>() fun calculateSalary() { for (person in allEmployees) { /* ... */ } } }
-
コンパニオンオブジェクト: クラス内で
companion
キーワードでマークされたコンパニオンオブジェクトは、クラスのインスタンスを必要とせずに呼び出されるメンバーを定義するためのメカニズムを提供します。コンパニオンオブジェクトはクラスのプライベートメンバーにアクセスでき、ファクトリメソッドや定数を保持するためによく使用されます。コンパニオンオブジェクトのメンバーはクラス名を使用してアクセスされ、Javaの静的メンバーと似ています。例えば、以下のUser
クラスにはファクトリメソッドを持つコンパニオンオブジェクトがあります。class User private constructor(val nickname: String) { companion object { fun newSubscribingUser(email: String) = User(email.substringBefore('@')) fun newSocialUser(accountId: Int) = User(getNameFromSocialNetwork(accountId)) } }
-
通常のオブジェクトとしてのコンパニオンオブジェクト: コンパニオンオブジェクトは通常のオブジェクトであり、名前を持つことができ、インターフェースを実装したり、拡張関数やプロパティを持つことができます。この柔軟性により、クラスレベルの振る舞いを柔軟に実装することが可能です。
-
オブジェクト式: オブジェクト式は、Kotlinで匿名オブジェクトを作成する簡潔な方法を提供し、Javaの匿名内部クラスに相当します。インターフェースを実装したり、メソッドを定義したりすることができ、周囲のスコープ内の変数を変更することも可能です。
-
-
4.5 オーバーヘッドなしで追加の型安全性を実現: インラインクラス: インラインクラスは、オブジェクトの割り当てによるパフォーマンスのオーバーヘッドなしで型安全性を強化する方法を提供します。
value
キーワードと@JvmInline
アノテーションでマークされ、ランタイムでは基になるプロパティ値として表され、ラッパーオブジェクトの作成を回避します。-
インラインクラスの要件: インラインクラスは、プライマリコンストラクタで初期化された1つのプロパティを持つ必要があり、クラス階層に参加することはできません。ただし、インターフェースを実装したり、メソッドを定義したり、計算されたプロパティを持つことは可能です。
-
ランタイムの表現: Kotlinコンパイラは、インラインクラスを可能な限り基になる型として表し、パフォーマンスを最適化します。
-
ユースケース: インラインクラスは、単位の測定を表現したり、プリミティブ型の型安全なラッパーを作成したり、同じ基になる表現を持ちながら異なる意味を持つ文字列を区別するために一般的に使用されます。
-
interfaceは実装も持てる
関数よりもプロパティを優先するケース
- 例外をスローしない
- 計算コストが安い(あるいは初回実行時にキャッシュされる)
- オブジェクトの状態が変化していなければ、複数回実行しても同じ結果を返す。
そうでなければ、代わりに関数を使うことを検討してください。
初期値にthisが使える
class Customer(val name: String, val postalCode: Int) {
/* ... */
fun copy(name: String = this.name,
postalCode: Int = this.postalCode) =
Customer(name, postalCode)
}
コンパニオンオブジェクトはインスタンスから呼び出せない
コンパニオンオブジェクトの拡張関数の定義
// business logic module
class Person(val firstName: String, val lastName: String) {
companion object {
}
}
// client/server communication module
fun Person.Companion.fromJSON(json: String): Person {
/* ... */
}
val p = Person.fromJSON(json)
inlineクラス、ValueObjectを表現するときに使えそう
interface PrettyPrintable {
fun prettyPrint()
}
@JvmInline
value class UsdCent(val amount: Int): PrettyPrintable {
val salesTax get() = amount * 0.06
override fun prettyPrint() = println("${amount}¢")
}
- KotlinのインターフェースはJavaと似ているが、デフォルトの実装とプロパティを含むことができる。
- デフォルトでは、すべての宣言はfinalでpublicです。
- 宣言をfinalでなくするには、openとマークする。
- 内部宣言は同じモジュール内で見ることができる。
- 入れ子になったクラスは、デフォルトでは内部ではありません。外部クラスへの参照を格納するには、キーワード inner を使用します。
- シールされたクラスのすべての直接のサブクラスと、シールされたインターフェースのすべての実装は、コンパイル時に知っておく必要があります。
- 初期化ブロックと 2 次 コンストラクタは、クラス・インスタンスを初期化するための柔軟性を提供します。
- フィールド識別子を使用して、アクセサー本体からプロパティー・バッキング・フィールドを参照します。
- データ・クラスは、コンパイラが生成したequals、hashCode、toString、copyなどのメソッドを提供します。
- クラスの委譲は、コード内の多くの類似した委譲メソッドを回避するのに役立つ。
- オブジェクト宣言は、シングルトン・クラスを定義するKotlinの方法である。
- コンパニオンオブジェクトは、(パッケージレベルの関数やプロパティとともに)Javaのスタティックメソッドやフィールド定義の代わりになります。
- コンパニオンオブジェクトは、他のオブジェクトと同様に、インターフェースを実装したり、拡張関数やプロパティを持つことができます。
- オブジェクト式はKotlinのJavaの匿名インナークラスに代わるもので、複数のインターフェースを実装したり、オブジェクトが作成されたスコープで定義された変数を変更したりする機能などが追加されています。
- インラインクラスを使用すると、プログラムに型安全性のレイヤーを導入することができ、同時に、短命のオブジェクトを多数割り当てることによる潜在的なパフォーマンスの低下を避けることができます。
5 Programming with lambdas
5.1 ラムダ式とメンバー参照
-
5.1.1 ラムダの紹介: コードブロックを値として扱う
ラムダは開発者に、コードブロックを値として扱い、関数に渡したり変数に格納したりする能力を与えます。これにより、ボタンのクリック処理のような振る舞いを簡潔に表現できます。
button.setOnClickListener { println("I was clicked!") }
ラムダ
{ println("I was clicked!") }
はボタンのクリック動作をカプセル化しています。 -
5.1.2 ラムダとコレクション
Kotlinはラムダを利用して豊富な標準ライブラリを提供し、コレクションで関数型プログラミングの技法を可能にしています。
filter
やmap
のような関数はこの調和を体現し、コレクションの操作をエレガントに行うツールを提供しています。 -
5.1.3 ラムダ式の構文
Kotlinのラムダは中括弧内に記述され、引数と本体を矢印 (
->
) で分けます。ラムダは変数に格納され、関数のように呼び出すことができます。val sum = { x: Int, y: Int -> x + y } println(sum(1, 2)) // 出力: 3
Kotlinは構文の柔軟性も提供しており、ラムダが関数呼び出しの最後の引数である場合、ラムダを括弧の外に移動することが可能です。
-
5.1.4 スコープ内の変数へのアクセス
ラムダは外部の変数をキャプチャして、囲むスコープからの変数にアクセスし、場合によってはそれを変更することができます。これにより、より柔軟なコード構造が可能になります。
fun printMessagesWithPrefix(messages: Collection<String>, prefix: String) { messages.forEach { println("$prefix $it") } }
この例では、ラムダが
prefix
変数をキャプチャし、各メッセージにprefix
を付けています。 -
5.1.5 メンバー参照
メンバー参照は、特定のメソッドやプロパティを直接呼び出す関数値を作成するための省略記法を提供します。
::
演算子はクラス名と参照するメンバーを結びつけます。people.maxByOrNull(Person::age)
Person::age
はPerson
クラスのage
プロパティを指しています。 -
5.1.6 バウンド可能な参照
バウンド可能な参照は、特定のオブジェクトインスタンスにおけるメンバー関数やプロパティを参照する方法を提供します。これらは引数を取らず、バウンドされたオブジェクトのメンバーの値を直接返します。例えば、
seb::age
は特定のseb
オブジェクトのage
プロパティを参照します。
5.2 Javaの関数型インターフェースの使用: シングルアブストラクトメソッド
-
5.2.1 Javaメソッドへのラムダの引数としての渡し方
KotlinのラムダはJavaの関数型インターフェース(SAMインターフェース)とシームレスに動作します。コンパイラはラムダを自動的にインターフェースのインスタンスに変換します。
button.setOnClickListener { println("I was clicked!") }
setOnClickListener
がJavaのメソッドであっても、KotlinのラムダはOnClickListener
の役割を果たします。 -
5.2.2 SAMコンストラクタ: ラムダを関数型インターフェースに明示的に変換する
SAMコンストラクタを使うことで、ラムダを関数型インターフェースのインスタンスに明示的に変換できます。これにより、メソッドからインターフェースのインスタンスを返す場合や、そのようなインスタンスを変数に格納する際に役立ちます。
val listener = OnClickListener { view -> /* ... */ }
この例では、SAMコンストラクタを使って
OnClickListener
インスタンスを作成しています。
fun
インターフェース
5.3 KotlinでのSAMインターフェースの定義: Kotlinは fun
インターフェースを提供しており、Kotlinコード内で関数型インターフェースを簡潔に定義する方法です。これらのインターフェースは、1つだけ抽象メソッドを持つ必要がありますが、複数の非抽象メソッドを含むこともでき、柔軟性と読みやすさを提供します。
fun interface IntCondition {
fun check(i: Int): Boolean
fun checkString(s: String) = check(s.toInt())
fun checkChar(c: Char) = check(c.digitToInt())
}
IntCondition
インターフェースは、抽象メソッド check
と追加のヘルパーメソッドを持つ fun
インターフェースを示しています。
with
、apply
、also
5.4 レシーバーを持つラムダ: -
5.4.1 同じオブジェクトに対する複数の操作:
with
with
関数は、同じオブジェクトに対する複数の操作を簡潔に行う方法を提供します。この関数はオブジェクトをレシーバーとして受け取り、ラムダ内で直接メソッドを呼び出せます。fun alphabet() = with(StringBuilder()) { for (letter in 'A'..'Z') { append(letter) } append("\nNow I know the alphabet!") toString() }
この
alphabet
関数内では、with
関数がStringBuilder
をレシーバーとして受け取り、ラムダ内でappend
やtoString
を直接呼び出しています。 -
5.4.2 オブジェクトの初期化と設定:
apply
関数apply
関数はビルダースタイルでオブジェクトの初期化と設定を行うのに優れています。この関数はオブジェクトをレシーバーとして受け取り、ラムダ内でそのプロパティを設定し、初期化されたオブジェクトを返します。val person = Person().apply { name = "Alice" age = 30 }
apply
関数は、Person
オブジェクトのname
とage
プロパティを設定するプロセスを簡潔にしています。 -
5.4.3 オブジェクトに対する追加のアクション:
also
also
関数は、オブジェクトをそのまま戻り値として保持しながら、追加のアクションを実行する必要がある場合に使用されます。この関数はオブジェクトを引数 (it
) としてラムダに渡します。val numbers = mutableListOf<Int>().also { println("Populating the list") for (i in 1..5) { it.add(i) } }
also
関数はメッセージを出力するなどの副作用を行いながらも、コードの流れを変えずにオブジェクトの操作を続けることができます。
- ラムダを使えば、コードの塊を関数の引数として渡せるので、一般的なコード構造を簡単に抽出できる。
- Kotlinでは、ラムダを括弧の外で関数に渡すことで、コードをすっきりと簡潔にすることができます。
- ラムダが1つのパラメーターしか取らない場合、暗黙の名前itでそれを参照できます。これにより、短くてシンプルなラムダで、唯一のラムダ・パラメータに明示的に名前を付ける手間を省くことができます。
- ラムダは外部変数を取り込むことができます。つまり、例えばラムダを呼び出している関数内の変数にラムダを使ってアクセスしたり変更したりすることができます。
- メソッド、コンストラクター、プロパティへの参照を作成するには、関数名の前に :: を付けます。このような参照をラムダの代わりに関数に渡すこともできます。
- ひとつの抽象メソッドでインターフェイスを実装する場合 (別名 SAM インターフェイス) は、インターフェイスを実装したオブジェクトを明示的に作成するのではなく、ラムダを渡します。
- レシーバ付きラムダは、特別なレシーバオブジェクトのメソッドを直接コールするためのラムダです。これらのラムダの本体は、周囲のコードとは異なるコンテキストで実行されるため、コードの構造化に役立ちます。
- with標準ライブラリ関数を使うと、オブジェクトへの参照を繰り返すことなく、同じオブジェクト上の複数のメソッドを呼び出すことができます。 applyを使うと、ビルダー型APIを使って、任意のオブジェクトを構築したり初期化したりすることができます。
第6章: コレクションとシーケンスの操作
この章では、Kotlinにおけるコレクション操作の世界を探り、データの変換や処理のための関数型プログラミングの技法を紹介し、シーケンスを用いた効率的な遅延評価のコレクション操作を説明します。
6.1 コレクション用の関数型API
Kotlinの標準ライブラリは、コレクションと調和して動作するように設計された様々な関数を提供しており、一般的なデータ処理タスクのための簡潔で表現力豊かなコードが可能です。これらの関数はしばしばラムダを引数として取り、開発者がその振る舞いをカスタマイズできるようにしています。
-
6.1.1 要素の削除と変換:
filter
とmap
filter
関数は、特定の条件を満たす要素だけを含む新しいコレクションを作成し、元のコレクションを指定した基準に基づいてフィルタリングします。val list = listOf(1, 2, 3, 4) println(list.filter { it % 2 == 0 }) // 出力:
この例では、リストから偶数のみを保持します。
map
関数はコレクション内の各要素を変換し、各要素に指定した関数を適用して、新しいコレクションを作成します。val list = listOf(1, 2, 3, 4) println(list.map { it * it }) // 出力:
この例では、リスト内の各要素を2乗しています。
-
6.1.2 コレクションの値を累積する:
reduce
とfold
reduce
関数はコレクション内の各要素に対して、与えられた関数を累積値に適用しながら反復処理を行い、最終的に1つの結果を生成します。初期の累積値には最初の要素が使用されます。val list = listOf(1, 2, 3, 4) val sum = list.reduce { acc, element -> acc + element } println(sum) // 出力: 10
この例では、
reduce
を使ってリストのすべての要素の合計を計算しています。fold
関数はreduce
と似ていますが、累積値の初期値を指定することができるため、異なる集約タスクに対してより柔軟に対応できます。val list = listOf(1, 2, 3, 4) val product = list.fold(1) { acc, element -> acc * element } println(product) // 出力: 24
この例では、
fold
を使ってリストのすべての要素の積を計算しています。 -
6.1.3 コレクションに対して述語を適用する:
all
、any
、none
、count
、およびfind
Kotlinはコレクション内の要素が指定した述語を満たすかどうかをチェックするためのいくつかの関数を提供しています。
-
all
: すべての要素が述語を満たす場合にtrue
を返します。 -
any
: 少なくとも1つの要素が述語を満たす場合にtrue
を返します。 -
none
: どの要素も述語を満たさない場合にtrue
を返します。 -
count
: 述語を満たす要素の数を返します。 -
find
: 述語を満たす最初の要素を返し、該当する要素がない場合はnull
を返します。
-
-
6.1.4 リストを2つのリストに分割する:
partition
partition
関数は、指定した述語に基づいてコレクションを2つのリストに分割し、述語を満たす要素と満たさない要素を分けます。val (even, odd) = list.partition { it % 2 == 0 }
この例では、リストを偶数と奇数に分割します。
-
6.1.5 リストをマップに変換する:
groupBy
groupBy
関数は、指定したキーセレクターに基づいてコレクションの要素をグループ化し、キーがキーセレクター関数の結果で、値が各グループに属する要素のリストであるマップを作成します。val people = listOf(Person("Alice", 31), Person("Bob", 29), Person("Carol", 31)) println(people.groupBy { it.age }) // 出力: {31=[Person(name=Alice, age=31), Person(name=Carol, age=31)], 29=[Person(name=Bob, age=29)]}
この例では、年齢ごとに人をグループ化しています。
-
6.1.6 コレクションをマップに変換する:
associate
、associateWith
、およびassociateBy
Kotlinはコレクションからマップを作成するためのいくつかの関数を提供しています。
-
associate
: 要素から生成されたキーと同じ要素から生成された値を関連付けることでマップを作成します。 -
associateWith
: コレクションの要素とそれらの要素から生成された値を関連付けてマップを作成します。 -
associateBy
: 要素から生成されたキーとその要素自身を関連付けてマップを作成します。
-
-
6.1.7 ミュータブルコレクションの要素の置き換え:
replaceAll
とfill
ミュータブルコレクションに対しては、要素を変更するための関数が提供されています。
-
replaceAll
: ミュータブルリストのすべての要素を、指定した変換関数を適用した結果で置き換えます。 -
fill
: ミュータブルリストを指定した値で埋めます。
-
-
6.1.8 コレクションの特別なケースの処理:
ifEmpty
ifEmpty
関数は、コレクションが空の場合にデフォルト値やアクションを指定する方法を提供します。 -
6.1.9 コレクションの分割:
chunked
とwindowed
Kotlinはコレクションを小さな部分に分割するための関数を提供しています。
-
chunked
: コレクションを指定したサイズのチャンクに分割します。 -
windowed
: コレクションの要素に対して、指定したサイズのスライドウィンドウを作成します。
-
-
6.1.10 コレクションの結合:
zip
zip
関数は2つのコレクションを1つのペアのコレクションに結合し、両方のコレクションの同じインデックスにある要素を組み合わせます。 -
6.1.11 入れ子のコレクションの要素の処理:
flatMap
とflatten
入れ子のコレクションを扱うために、Kotlinは次の関数を提供しています。
-
flatMap
: 各要素をコレクションに変換し、結果のコレクションを1つのコレクションにフラット化します。 -
flatten
: コレクションのコレクションを1つのコレクションにフラット化します。
-
6.2 Kotlinにおけるシーケンス
シーケンスは、コレクション操作を遅延実行する方法を提供し、大規模なコレクションに対する操作チェーンで中間コレクションの作成を避けることで、パフォーマンスを向上させます。
シーケンス操作の実行: 中間操作と終端操作
-
中間操作(例:
map
やfilter
)は別のシーケンスを返し、要素の変換方法を定義します。 -
終端操作(例:
toList
)は最終的な結果を返し、それまでのすべての中間操作の実行をトリガーします。
例: シーケンスの使用
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val result = people
.asSequence() // シーケンスに変換
.map(Person::name) // 中間操作
.filter { it.startsWith("A") } // 中間操作
.toList() // 終端操作
println(result) // 出力: [Alice]
この例では:
-
asSequence()
はリストをSequence
に変換します。 -
map
とfilter
は変換を定義しますが、すぐには実行されません。 -
toList
は終端操作であり、シーケンス全体の実行をトリガーします。
シーケンスの作成
-
asSequence()
: 既存のコレクションをSequence
に変換します。 -
generateSequence()
: 指定したロジックに基づいてシーケンスを生成します。
例: generateSequence()
val naturalNumbers = generateSequence(0) { it + 1 }
// 自然数のシーケンスを生成
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
// 条件が満たされる間は要素を取得
println(numbersTo100.sum()) // 出力: 5050
この例では:
-
generateSequence()
は自然数の無限シーケンスを生成します。 -
takeWhile
はシーケンスを100以下の数値に制限します。 -
sum
は結果のシーケンスの合計を計算します。
重要なポイント
- シーケンスは操作を遅延実行し、大規模なコレクションに対して効率を向上させます。
- パフォーマンスが重要で中間コレクションが不要な場合は、シーケンスを使用します。
第7章: Null可能な値の操作
この章では、KotlinのNullPointerException
を回避するためのNull可能な値の扱い方について説明します。これはKotlinがJavaと異なる特徴の一つであり、より信頼性の高いコードを書くことに寄与します。
NullPointerException
を回避し、値の欠如を扱う: Null可能性
7.1 Null可能性はKotlinの型システムの中核概念であり、コンパイル時にNullPointerException
の可能性を検出することを可能にします。null
を保持できる変数を明示的にマークすることで、Kotlinはランタイムでクラッシュを引き起こすような危険な操作を防止します。このエラー検出のシフトはランタイムからコンパイル時に移り、コードの信頼性を高めます。
7.2 Nullの可能性のある変数をNullable型で明示する
Kotlinでは、Nullable型とNon-Nullable型を区別します。デフォルトでは型はNon-Nullableであり、null
を保持することはできません。null
を保持できる変数を宣言するには、型に?
を付けます。この明示性により、どの変数がnull
になる可能性があるかが明確になり、コンパイラが安全な操作を強制します。
fun strLenSafe(s: String?) = ... // sはnullである可能性がある
7.3 型の意味を深く理解する
プログラミングにおける型の概念は、可能な値の集合とその値に対して実行可能な操作を含みます。KotlinのNullable型システムは、安全で予測可能な操作を保証します。Nullable型では、メソッドを直接呼び出すといった操作が制限されており、NullPointerException
のエラーを防ぎます。
?.
で組み合わせる
7.4 Nullチェックとメソッド呼び出しを安全呼び出し演算子安全呼び出し演算子?.
は、nullチェックとメソッド呼び出しを1つの式で行うための簡潔な方法を提供します。レシーバがnullでない場合はメソッドが呼び出され、そうでなければ式はnull
として評価されます。これにより冗長なif (variable != null)
チェックを回避できます。
val length = str?.length // strがnullならlengthもnull
?:
7.5 Nullの場合にデフォルト値を提供するElvis演算子Elvis演算子?:
は、式がnull
になる可能性がある場合にデフォルト値を提供する便利な方法です。Elvis演算子の左側の式がnull
でない場合、その値が返され、それ以外の場合は右側の式が使用されます。
val name = person?.name ?: "Unknown" // nameがnullの場合は"Unknown"を使用
as?
7.6 例外を投げずに値を安全にキャストする: 安全キャスト演算子as?
は、値を指定された型にキャストしようとします。キャストが成功した場合はキャストされた値が返され、失敗した場合は式がnull
として評価されます。これにより、通常のas
演算子で発生する可能性のあるClassCastException
エラーを回避できます。
val person = obj as? Person // キャストが失敗した場合personはnull
!!
7.7 コンパイラに約束をする: Non-Nullアサーション演算子Non-Nullアサーション演算子!!
は、Nullable型を強制的にNon-Nullable型に変換します。値がnull
の場合、NullPointerException
がスローされます。この演算子は、値が絶対にnull
でないと確信できる場合にのみ使用し、KotlinのNull安全チェックをバイパスします。
val length = str!!.length // strがnullの場合は例外がスローされる
let
関数
7.8 Nullableな式の処理: let
関数は、安全呼び出し演算子と共に使用すると、Nullableな式がnull
でない場合にのみコードブロックを実行するための簡潔な方法を提供します。let
ブロック内で明示的なNullチェックをせずに、Non-Nullな値に対して操作を行うことができます。
email?.let { sendEmailTo(it) } // emailがnullでない場合のみsendEmailToを呼び出す
Function | Access to X vla | Return value |
X. let { ... ... } | it | Result of lambda |
X. also { ... } | it | |
x.apply { ... } ... | this | |
x.run { ... } | this | Result of lambda |
with (x) { ... } | this | Result of lambda |
7.9 即時初期化なしのNon-Null型: 遅延初期化プロパティ
lateinit
修飾子を使用すると、オブジェクトの構築後に初期化されるNon-Nullableプロパティを宣言することができます。これは、後で初期化が保証される場合に便利であり、例えばライフサイクルメソッドを持つフレームワークで役立ちます。初期化前にlateinit
プロパティにアクセスすると、UninitializedPropertyAccessException
がスローされます。
lateinit var myService: MyService
// ...
myService = MyService()
7.10 安全呼び出し演算子なしでの拡張: Nullable型の拡張
Kotlinでは、Nullable型に対する拡張関数を定義することが可能であり、関数内でnull
の場合の処理を行うことができます。これらの関数は、Nullableな値に対して安全呼び出し演算子を使わずに直接呼び出すことができ、内部でnullを処理します。
fun String?.isNullOrBlank(): Boolean = this == null || this.isBlank()
7.11 型パラメータのNull可能性
デフォルトでは、Kotlinのジェネリック型パラメータはNullableであり、Nullable型とNon-Nullable型の両方を受け入れます。型パラメータをNon-Nullable型に制限するには、Any
などのNon-Nullableな上限を指定します。
fun <T: Any> printHashCode(t: T) { ... } // TはNon-Nullableでなければならない
7.12 Null可能性とJava
KotlinはJavaコードともシームレスに連携し、Javaが組み込みでNull可能性をサポートしていない場合でも対応します。KotlinはJava型を次のように扱います。
-
Null可能性アノテーションの認識: Javaコードが
@Nullable
や@NotNull
などのNull可能性アノテーションを使用している場合、Kotlinはそれを尊重し、Nullable型やNon-Nullable型にマッピングします。 - プラットフォーム型: Null可能性アノテーションのないJava型に対して、Kotlinはプラットフォーム型として扱い、Null可能性が不明な状態になります。Kotlinはプラットフォーム型をNullableまたはNon-Nullableのどちらとしても扱うことができ、Null安全の責任は開発者に委ねられます。
- 継承: KotlinでJavaメソッドをオーバーライドする際、JavaコードからのNullの可能性を考慮して、パラメータや戻り値をNullableまたはNon-Nullableとして宣言するかを選ぶことができます。
第8章: 基本型、コレクション、配列
8.1 プリミティブ型とその他の基本型
8.1.1 整数、浮動小数点数、文字、およびブール値の表現
KotlinではJavaと異なり、プリミティブ型とラッパー型を区別しません。常に同じ型を使用します。たとえば、Int
はプリミティブ型とラッパー型の両方に使用されます。
Kotlinは実行時に数値型を可能な限り効率的に表現します。Int
型は通常、Javaのプリミティブ型int
にコンパイルされます。ただし、コレクションなどのジェネリッククラスの型引数として使用される場合、対応するJavaラッパー型(例: java.lang.Integer
)にコンパイルされます。
以下はKotlinの型と対応するJavaのプリミティブ型の一覧です:
- 整数型:
Byte
、Short
、Int
、Long
- 浮動小数点数型:
Float
、Double
- 文字型:
Char
- ブール型:
Boolean
8.1.2 正の数を表現するためのフルビット範囲の使用: 符号なし数値型
符号なし整数型は、正の値を表現するために整数のフルビット範囲を使用する必要がある場合に使用します。Kotlinは4つの符号なし数値型を提供しています: UByte
、UShort
、UInt
、ULong
。これらはプリミティブ型と同じパフォーマンス特性を持っています。
Kotlinでは符号なし数値型は既存の符号付きプリミティブを基にしたインラインクラスとして実装されています。たとえば、UInt
は内部的にInt
を使用して実装されています。
Int?
、Boolean?
など
8.1.3 Nullableプリミティブ型: KotlinのNullableプリミティブ型は対応するラッパー型にコンパイルされます。null
はJavaの参照型の変数にのみ格納できるためです。たとえば、Int?
はjava.lang.Integer
にコンパイルされます。
8.1.4 Kotlinは数値の変換を明示的にする
Kotlinは、予期しない挙動を避けるために明示的な数値の変換を要求します。toByte()
、toShort()
、toChar()
などの変換関数を使用して、小さい型を大きい型に変換したり、大きい型を小さい型に切り詰めたりします。
Any
とAny?
: Kotlin型階層のルート
8.1.5 Any
はKotlinにおけるすべてのNon-Nullable型のスーパータイプです。JavaのObject
に似ていますが、すべての型のスーパータイプであり、プリミティブ型も含まれます。
Any?
は、null
を含む可能性のあるすべての値を保持する変数が必要な場合に使用します。
Unit
型: Kotlinのvoid
8.1.6 Unit
型はJavaのvoid
に相当します。何も返さない関数の戻り値の型として使用されます。
Nothing
型: "この関数は決して返らない"
8.1.7 Nothing
型は決して存在しない値を表します。通常の方法で決して戻らない関数(常に例外をスローする関数など)の戻り値の型として使用されます。
8.2 コレクションと配列
8.2.1 Nullable値のコレクションとNullableなコレクション
Kotlinは型引数のNull可能性をサポートしているため、コレクションはnull
値を保持することができます。例えば、List<Int?>
はInt
値またはnull
を保持するリストを表します。
8.2.2 読み取り専用コレクションとミュータブルコレクション
Kotlinは、コレクション内のデータにアクセスするためのインターフェースとデータを変更するためのインターフェースを分離しています。kotlin.collections.Collection
インターフェースはデータへのアクセスに使用され、kotlin.collections.MutableCollection
インターフェースはCollection
インターフェースを拡張し、データの変更用のメソッドを提供します。
8.2.3 KotlinのコレクションとJavaのコレクションは深く関連している
KotlinはJavaのコレクションクラスをそのまま使用しますが、各Javaコレクションインターフェースに対して読み取り専用とミュータブルの表現を提供します。
8.2.4 Javaで宣言されたコレクションはKotlinではプラットフォーム型として扱われる
Javaで宣言されたコレクションはKotlinではプラットフォーム型として扱われ、そのミュータビリティ(可変性)は不明です。読み取り専用またはミュータブルとして扱うことができます。
8.2.5 相互運用性とパフォーマンスのためのオブジェクトとプリミティブ型の配列の作成
KotlinではArray
クラスで配列が表されます。以下の関数を使用して配列を作成できます:
-
arrayOf
: 指定した要素を含む配列を作成します。 -
arrayOfNulls
: 指定したサイズのnull
要素を含む配列を作成します。 -
Array
コンストラクタ: 配列のサイズとラムダを取り、各配列要素をラムダを呼び出して初期化します。
また、Kotlinはボクシングを行わないプリミティブ型の配列専用のクラスも提供しています(IntArray
、ByteArray
、CharArray
など)。
以下は配列を作成する例です:
val letters = Array(26) { i -> ('a' + i).toString() }
val fiveZeros = IntArray(5)
val fiveZerosToo = intArrayOf(0, 0, 0, 0, 0)
val squares = IntArray(5) { i -> (i+1) * (i+1) }
10
11
12 Reflection
12.1.7 Generic classes as annotation parameters
interface ValueSerializer<T> {
fun toJsonValue(value: T): Any?
fun fromJsonValue(jsonValue: Any?): T
}
annotation class CustomSerializer(
val serializerClass: KClass<out ValueSerializer<*>>
)