【初心者向け】Kotlin入門ガイド:ゼロから始めるモダンプログラミング
はじめに:Kotlinの世界へようこそ
プログラミングの世界へようこそ!数あるプログラミング言語の中から、もしあなたが今「Kotlin」に興味を持っているなら、それは素晴らしい選択です。Kotlinは、現代的で簡潔、そして安全なプログラミング言語として、世界中の開発者から注目を集めています。特に、Androidアプリ開発の公式言語となったことで、その知名度は一気に広まりました。
この記事は、プログラミングが初めての方、あるいは他の言語を少し触ったことがあるけれどKotlinは初めてという初心者の方に向けて書かれています。Kotlinの基本的な使い方から、その強力な機能、そしてなぜKotlinが選ばれるのかまで、詳細かつ丁寧に解説していきます。約5000語に渡るこの記事を読み終える頃には、Kotlinの基礎をしっかりと理解し、簡単なプログラムが書けるようになっているはずです。
Kotlinとは何か?
Kotlinは、JetBrains社によって開発された静的型付けのプログラミング言語です。Java仮想マシン(JVM)上で動作するだけでなく、JavaScriptへコンパイルしたり、ネイティブコードへコンパイルすることも可能です。このため、Androidアプリ開発はもちろん、サーバーサイド開発、デスクトップアプリケーション開発、さらにはフロントエンド開発やマルチプラットフォーム開発にも利用できます。
なぜKotlinを学ぶのか?
Kotlinを学ぶ理由はたくさんありますが、初心者にとって特に魅力的な点をいくつか挙げましょう。
- 簡潔で読みやすいコード: KotlinはJavaに比べてコード量が少なく、同じ処理でもより簡潔に書くことができます。これにより、コードが読みやすくなり、初心者でも理解しやすくなります。
- Null安全: NullPointerException(ナルポインタ例外)は、多くのプログラマを悩ませるエラーの一つです。Kotlinは、コンパイル時にNullになりうるかどうかを厳密にチェックする「Null安全」の仕組みを備えており、この種のエラーを劇的に減らすことができます。
- Javaとの完全な相互運用性: KotlinはJVM言語なので、既存のJavaライブラリやフレームワークをそのまま利用できます。また、KotlinコードからJavaコードを呼び出したり、逆にJavaコードからKotlinコードを呼び出したりすることも簡単です。これは、既存のJavaプロジェクトに少しずつKotlinを導入していく場合にも非常に便利です。
- 高い生産性: 簡潔なコード、強力なIDEサポート、そしてモダンな言語機能により、開発効率が向上し、より短時間でプログラムを開発できます。
- 活発なコミュニティ: Kotlinは非常に人気があり、世界中に活発な開発者コミュニティがあります。困ったことがあれば、コミュニティに質問したり、豊富なオンラインリソースを参照したりすることができます。
この記事で学ぶこと
この記事では、以下の内容を順番に学んでいきます。
- Kotlinの開発環境の準備
- Kotlinの基本的な構文(変数、型、制御構造など)
- 関数について
- クラスとオブジェクト指向プログラミングの基礎
- Kotlinの便利な機能(Null安全、コレクション操作、ラムダ式など)
- 簡単なコルーチン(非同期処理)の紹介
- Kotlinの応用分野
- 今後の学習リソース
さあ、Kotlinを使ったプログラミングの冒険に出かけましょう!
開発環境の準備
プログラミングを始めるには、まずコードを書くための環境が必要です。Kotlinの開発には、JetBrains社が提供する統合開発環境(IDE)である「IntelliJ IDEA」が最もよく使われます。IntelliJ IDEAはKotlinの開発元が作っているため、Kotlinの開発体験が非常に優れています。
ここでは、IntelliJ IDEA Community Edition(無償版)を使って環境を準備する方法を説明します。
1. IntelliJ IDEA Community Editionのダウンロード
以下の公式サイトから、IntelliJ IDEA Community Editionをダウンロードします。
https://www.jetbrains.com/idea/download/
お使いのOS(Windows, macOS, Linux)に合ったバージョンを選択し、「Community」版をダウンロードしてください。
2. IntelliJ IDEAのインストール
ダウンロードしたインストーラを実行し、画面の指示に従ってインストールを進めます。特別な設定は不要な場合が多いですが、必要に応じてインストール先フォルダなどを変更してください。インストールが完了したら、IntelliJ IDEAを起動します。
初回起動時には、設定のインポートやライセンス同意などが求められる場合がありますので、適切に対応してください。
3. 新しいKotlinプロジェクトの作成
IntelliJ IDEAが起動したら、新しいプロジェクトを作成します。
- ウェルカム画面が表示されている場合は、「New Project」を選択します。
- すでにプロジェクトが開いている場合は、メニューから「File」→「New」→「Project…」を選択します。
プロジェクト作成ダイアログが表示されます。
- 左側のリストから「New Project」を選択します。
- 右側の設定で、以下の項目を確認または設定します。
- Name: プロジェクト名を入力します。(例:
MyFirstKotlinApp
) - Location: プロジェクトファイルを保存する場所を指定します。
- Language: ドロップダウンリストから「Kotlin」を選択します。
- Build system: 「IntelliJ」を選択します(MavenやGradleはより大規模なプロジェクト向けです。最初はIntelliJがシンプルです)。
- JDK: Java Development Kit (JDK) が選択されていることを確認します。もしJDKがインストールされていない、あるいは認識されていない場合は、ドロップダウンリストから「Download JDK…」を選択してインストールできます。KotlinはJVM上で動作するため、JDKが必要です。
- Add sample code: 「Add sample code」にチェックを入れておくと、
main
関数を持つサンプルファイルが自動的に作成されます。これは入門には便利です。
- Name: プロジェクト名を入力します。(例:
設定が完了したら、「Create」ボタンをクリックします。
4. Hello, world! プログラムの実行
プロジェクトが作成されると、IntelliJ IDEAの画面が表示されます。左側のプロジェクトビューに、作成したプロジェクトのファイル構造が表示されているはずです。
もし「Add sample code」にチェックを入れていれば、src
フォルダの中にMain.kt
のようなファイルが作成されています。このファイルを開いてみましょう。以下のようなコードが書かれています。
kotlin
fun main() {
println("Hello, World!")
}
このコードは、画面に「Hello, World!」と表示する、プログラミングで最も基本的なプログラムです。
プログラムを実行するには、いくつかの方法があります。
- コードエディタの左余白にある緑色の再生ボタン(▶)をクリックし、「Run ‘MainKt’」を選択します。
- メニューから「Run」→「Run ‘MainKt’」を選択します。
- Shift + F10(Windows/Linux)または Control + R(macOS)のショートカットキーを使います。
実行すると、IntelliJ IDEAの下部に「Run」ウィンドウが表示され、プログラムの出力が表示されます。
“`
Hello, World!
Process finished with exit code 0
“`
これで、Kotlinの開発環境が整い、最初のプログラムを実行することができました!おめでとうございます!
もしサンプルコードが自動生成されなかった場合は、src
フォルダを右クリックし、「New」→「Kotlin Class/File」を選択して「File」を選び、ファイル名にMain.kt
と入力してOKをクリックしてください。そして、上記のfun main() { ... }
のコードをファイルに貼り付けて保存し、実行してみてください。
Kotlinの基本構文
ここからは、Kotlinのコードの基本的な書き方について詳しく見ていきましょう。
1. コメント
コードの中にメモを残したり、一時的にコードを実行しないようにしたりするためにコメントを使います。Kotlinには2種類のコメントがあります。
-
単一行コメント:
//
から行末までがコメントになります。kotlin
fun main() {
// これは単一行コメントです
println("Hello") // ここから行末までもコメントです
} -
複数行コメント:
/*
から*/
までがコメントになります。コメントの中にさらに複数行コメントを入れることも可能です(ネスト可能)。kotlin
/*
これは複数行コメントです。
複数行にわたってコメントを書く場合に便利です。
/* ネストもできます */
*/
fun main() {
println("World")
}
コメントはプログラムの実行には影響しませんが、コードの可読性を高めるために非常に重要です。
2. パッケージとimport
Kotlinのファイルは通常、パッケージに属します。パッケージは、関連するコードをまとめて管理するための仕組みです。ファイルの先頭にpackage
キーワードを使って宣言します。
“`kotlin
package mypackage.myapp
fun main() {
// …
}
“`
他のパッケージで定義されたクラスや関数を利用するには、import
キーワードを使います。Javaと同じように使えます。
“`kotlin
package anotherpackage
import mypackage.myapp.someFunction // 特定の関数をインポート
import mypackage.myapp.* // mypackage.myappパッケージ内の全てのものをインポート
fun main() {
someFunction() // インポートしたので直接呼び出せる
}
“`
標準ライブラリのよく使うものは自動的にインポートされるため、自分でimport
を書く必要がない場合が多いです。
3. 関数 (funキーワード)
処理のまとまりは「関数」として定義します。Kotlinではfun
キーワードを使って関数を定義します。
kotlin
fun 関数名(引数名: 引数の型, ...): 戻り値の型 {
// 関数の本体
return 戻り値
}
戻り値がない場合は、戻り値の型を省略するか、Unit
と記述します。Unit
はJavaのvoid
に相当しますが、Kotlinでは実際にはUnit
型のオブジェクトを返しています(通常は意識する必要はありません)。
例:
“`kotlin
// 戻り値がない関数
fun sayHello(name: String): Unit {
println(“Hello, $name!”)
}
// 戻り値を返す関数
fun add(a: Int, b: Int): Int {
return a + b
}
fun main() {
sayHello(“Kotlin”) // 関数呼び出し
val result = add(5, 3)
println(“5 + 3 = $result”)
}
“`
4. main関数
Kotlinのプログラムは、特別な関数であるmain
関数から実行が開始されます。main
関数は、引数なしまたはArray<String>
型の引数を取る形で定義できます。
“`kotlin
fun main() {
// プログラムの開始点
println(“プログラムを開始します”)
}
// コマンドライン引数を受け取る場合
fun main(args: Array
println(“プログラムを開始します。引数は ${args.size}個です。”)
for (arg in args) {
println(“引数: $arg”)
}
}
“`
通常は引数なしのmain
関数を使います。
5. printlnとprint
画面に文字を表示するには、標準ライブラリのprintln
関数またはprint
関数を使います。
println
: 文字列を表示した後、改行します。print
: 文字列を表示した後、改行しません。
kotlin
fun main() {
print("これは改行しない")
println("文字列です。printlnは改行します。")
println("新しい行です。")
}
実行結果:
これは改行しない文字列です。printlnは改行します。
新しい行です。
println
やprint
の中では、文字列リテラル(ダブルクォーテーションで囲まれた文字列)を使います。また、文字列テンプレート(String Template)を使って、変数や式の結果を文字列中に埋め込むことができます。
kotlin
fun main() {
val name = "Alice"
val age = 30
println("私の名前は$nameです。年齢は$age歳です。") // $変数の名前
println("来年${age + 1}歳になります。") // ${式}
}
実行結果:
私の名前はAliceです。年齢は30歳です。
来年31歳になります。
$
の後に変数の名前を書くだけで、その変数の値が文字列に埋め込まれます。より複雑な式の結果を埋め込みたい場合は、${}
を使います。
6. 変数 (valとvar)
プログラムでは、データを一時的に保存しておくために「変数」を使います。Kotlinには、変数を宣言するためのキーワードが2種類あります。
-
val
: Value(値)の略。一度代入すると、それ以降値を変更できない変数(読み取り専用変数)を宣言します。Javaでいうfinal変数に近いです。kotlin
val message = "Hello"
// message = "World" // エラー! val変数は再代入できません -
var
: Variable(変数)の略。値を何度でも変更できる変数を宣言します。kotlin
var count = 0
count = count + 1 // 値を変更できます
println(count) // 出力: 1
どちらを使うべきか迷ったら、まずはval
を使うことをお勧めします。値を変更する必要がある場合にのみvar
を使います。これにより、意図しない値の変更を防ぎ、プログラムの安全性を高めることができます。
7. データ型 (基本型: Int, Double, Boolean, Stringなど)
変数には、扱うデータの種類に応じた「型」があります。Kotlinは静的型付け言語なので、変数を宣言するとき、またはコンパイラが型を推論するときに、その変数がどのような型のデータを保持するかが決まります。
Kotlinの主な基本型は以下の通りです。
-
数値型:
Byte
: 8ビット整数 (-128 から 127)Short
: 16ビット整数 (-32768 から 32767)Int
: 32ビット整数 (-2^31 から 2^31-1)。最もよく使われる整数型。Long
: 64ビット整数 (-2^63 から 2^63-1)。大きな整数を扱う場合。リテラルの末尾にL
をつけます。(例:100L
)Float
: 32ビット単精度浮動小数点数。リテラルの末尾にf
またはF
をつけます。(例:1.23f
)Double
: 64ビット倍精度浮動小数点数。最もよく使われる浮動小数点数型。
-
論理値型:
Boolean
: 真偽値を表す型。true
またはfalse
のいずれかの値を取ります。
-
文字型:
Char
: 16ビットUnicode文字。シングルクォーテーションで囲みます。(例:'A'
,'あ'
)
-
文字列型:
String
: 文字の並び。ダブルクォーテーションで囲みます。(例:"Hello, Kotlin!"
)文字列は不変(immutable)です。一度作成した文字列の内容を変更することはできません。
型の指定
変数を宣言する際、変数名の後にコロン(:
)と型名を記述することで、明示的に型を指定できます。
kotlin
val age: Int = 30
val pi: Double = 3.14159
var isAdult: Boolean = true
val firstLetter: Char = 'K'
val greeting: String = "Hello"
8. 型推論
Kotlinの強力な機能の一つに「型推論」があります。変数を宣言する際に初期値を代入する場合、コンパイラがその初期値から変数の型を自動的に判断してくれます。このため、多くの場合、自分で型を明示的に書く必要はありません。
“`kotlin
val age = 30 // コンパイラがInt型と推論する
val pi = 3.14159 // コンパイラがDouble型と推論する
var isAdult = true // コンパイラがBoolean型と推論する
val firstLetter = ‘K’ // コンパイラがChar型と推論する
val greeting = “Hello” // コンパイラがString型と推論する
// 明示的に型を指定した場合と同じ意味になる
val age: Int = 30
val pi: Double = 3.14159
// …
“`
型推論を使うことでコードがより簡潔になりますが、複雑な場合は明示的に型を指定した方が分かりやすいこともあります。
9. Nullable型 (?) と NonNull型
KotlinのNull安全の仕組みは、このNullable型とNonNull型によって実現されています。
-
NonNull型: 値として
null
を許容しない型です。デフォルトでは、Kotlinの変数はNonNull型です。kotlin
var name: String = "Kotlin"
// name = null // エラー!NonNull型にはnullを代入できません -
Nullable型: 値として
null
を許容する型です。型名の後ろにクエスチョンマーク(?
)をつけて宣言します。kotlin
var middleName: String? = "Charles" // nullを許容するString型
middleName = null // nullを代入できます
NonNull型の変数には絶対にnull
が入らないことが保証されるため、その変数を使う際にNullPointerExceptionを心配する必要がありません。Nullable型の変数を扱う際には、null
である可能性を考慮した特別な処理が必要になります。このNullable型を安全に扱う方法については、「より進んだKotlinの機能」の章で詳しく説明します。
10. if式
条件によって処理を分けたい場合は、if
式を使います。Kotlinのif
は、Javaのif文と同様に使うこともできますが、値を返す「式」として使うこともできます。
“`kotlin
fun main() {
val score = 85
// 文として使う場合 (Javaと同じように)
if (score >= 60) {
println("合格です")
} else {
println("不合格です")
}
// 式として使う場合 (値を代入できる)
val result = if (score >= 60) {
"合格" // ifブロックの最後の式が値となる
} else {
"不合格" // elseブロックの最後の式が値となる
}
println("判定: $result")
// もっと簡潔に書くことも可能
val result2 = if (score >= 80) "優" else if (score >= 70) "良" else "可"
println("評価: $result2")
}
“`
式として使う場合、if
やelse
のブロックの最後の行の式の結果が、if
式全体の値となります。これにより、三項演算子(? :
)のような使い方が、より読みやすく実現できます。
11. when式
複数の条件分岐を扱う場合に便利なのがwhen
式です。Javaのswitch
文に似ていますが、より強力で柔軟です。when
も式として値を返すことができます。
“`kotlin
fun main() {
val dayOfWeek = 3
// 文として使う場合
when (dayOfWeek) {
1 -> println("月曜日")
2 -> println("火曜日")
3 -> println("水曜日")
4 -> println("木曜日")
5 -> println("金曜日")
6, 7 -> println("週末") // 複数の値をカンマで指定
else -> println("不正な値") // どの条件にも一致しない場合
}
// 式として使う場合
val dayType = when (dayOfWeek) {
1, 2, 3, 4, 5 -> "平日"
6, 7 -> "週末"
else -> "不明"
}
println("$dayOfWeekは$dayTypeです。")
// 範囲や型による条件分岐も可能
val value: Any = 10 // Anyは任意の型を表す
when (value) {
is Int -> println("整数です") // isキーワードで型をチェック
is String -> println("文字列です")
in 1..10 -> println("1から10の範囲内です") // inキーワードで範囲をチェック
!in 20..30 -> println("20から30の範囲外です") // !inで範囲外をチェック
else -> println("それ以外です")
}
}
“`
when
式は、様々な条件を指定できます。
* 特定の値(1 -> ...
)
* 複数の特定の値(6, 7 -> ...
)
* 範囲内の値(in 1..10 -> ...
)
* 特定の型であるか(is Int -> ...
)
式として使う場合、全ての可能性を網羅している必要があります(例えば、else
ブランチを提供するか、sealed class
やenum class
で全てのケースを網羅する)。
12. forループ
繰り返し処理を行うにはループを使います。Kotlinのfor
ループは、Javaの拡張forループ(for-eachループ)に似ています。コレクションや範囲内の要素を順番に取り出して処理する場合によく使われます。
“`kotlin
fun main() {
// 範囲をループ
for (i in 1..5) { // 1から5まで (5を含む)
println(i)
}
println("---")
for (i in 1 until 5) { // 1から5未満まで (5を含まない)
println(i)
}
println("---")
for (i in 5 downTo 1) { // 5から1まで逆順に
println(i)
}
println("---")
for (i in 1..10 step 2) { // 1から10まで、2ステップずつ
println(i)
}
println("---")
// 配列やリストなどのコレクションをループ
val fruits = listOf("Apple", "Banana", "Cherry") // 後で詳しく説明します
for (fruit in fruits) {
println(fruit)
}
println("---")
// インデックスと一緒にループ
for (index in fruits.indices) { // インデックスの範囲を取得
println("Index $index: ${fruits[index]}")
}
println("---")
// インデックスと値の両方を取得
for ((index, value) in fruits.withIndex()) {
println("Index $index: $value")
}
}
“`
Kotlinのfor
ループは非常に柔軟で、..
演算子で範囲を生成したり、until
で末尾を含めない範囲、downTo
で逆順、step
でステップ幅を指定できます。コレクションに対しては、要素を直接取り出すだけでなく、indices
プロパティでインデックスをループしたり、withIndex()
関数でインデックスと値を同時に取得したりできます。
13. whileループ、do-whileループ
while
ループは、指定した条件が真である間、処理を繰り返します。ループに入る前に条件を判定します。
kotlin
fun main() {
var count = 0
while (count < 5) {
println("Count: $count")
count++
}
}
do-while
ループは、最初にブロック内の処理を一度実行し、その後で条件を判定します。条件が真である間、処理を繰り返します。少なくとも一度はブロック内の処理を実行したい場合に便利です。
kotlin
fun main() {
var count = 0
do {
println("Count: $count")
count++
} while (count < 5)
}
この例ではwhile
とdo-while
の出力は同じですが、もし初期値がcount = 5
だった場合、while
ループは一度も実行されませんが、do-while
ループは一度だけ実行されます。
14. breakとcontinue
ループの途中で繰り返し処理を中断したり、次の繰り返しに進めたりするためにbreak
とcontinue
を使います。
break
: ループ全体を終了します。continue
: 現在の繰り返し処理を中断し、次の繰り返し処理に進みます。
kotlin
fun main() {
for (i in 1..10) {
if (i == 3) {
continue // 3の場合はスキップして次の繰り返しへ
}
if (i == 7) {
break // 7になったらループを終了
}
println(i)
}
// 出力: 1, 2, 4, 5, 6
}
これらの基本的な構文を理解すれば、Kotlinで簡単なプログラムを書き始める準備ができます。次は、関数についてさらに掘り下げてみましょう。
関数についてさらに詳しく
関数は、プログラムを構成する基本的な要素です。同じ処理を繰り返し実行する場合や、処理を小さなまとまりに分割して分かりやすくする場合に使います。
1. 関数の定義と呼び出し
関数の基本的な定義方法はすでに説明しましたが、再度確認します。
kotlin
fun 関数名(パラメータ名: 型, ...): 戻り値の型 {
// 関数本体
// ...
return 戻り値 // 戻り値がある場合
}
関数を呼び出す際は、関数名の後にカッコ()
をつけ、必要な引数を渡します。
“`kotlin
fun greet(name: String) {
println(“こんにちは、$nameさん!”)
}
fun main() {
greet(“山田”) // 関数呼び出し
greet(“田中”) // 何度でも呼び出せる
}
“`
2. 引数と戻り値
引数(パラメータ)は、関数に渡す値です。関数はこれらの値を使って処理を行います。戻り値は、関数が処理の結果として返す値です。
“`kotlin
// 引数を2つ受け取り、その合計を返す関数
fun add(a: Int, b: Int): Int {
return a + b
}
// 引数を1つ受け取り、何も返さない関数 (戻り値はUnit)
fun printLength(text: String): Unit {
println(“文字列「$text」の長さは ${text.length} です。”)
}
fun main() {
val sum = add(10, 20) // 引数を渡して関数を呼び出し、戻り値を受け取る
println(“合計: $sum”) // 出力: 合計: 30
printLength("Kotlin") // 引数を渡して関数を呼び出し
}
“`
3. デフォルト引数
Kotlinでは、関数の引数にデフォルト値を設定できます。これにより、関数呼び出し時にその引数を省略した場合、デフォルト値が使われます。
“`kotlin
fun greet(name: String, greeting: String = “こんにちは”) {
println(“$greeting、$nameさん!”)
}
fun main() {
greet(“山田”) // greetingを省略 -> デフォルト値「こんにちは」が使われる
greet(“田中”, “おはよう”) // greetingを指定 -> 指定した値が使われる
}
“`
実行結果:
こんにちは、山田さん!
おはよう、田中さん!
デフォルト引数を使うと、同じような機能を持つ関数のオーバーロード(引数の型や数が違う同名関数)を減らすことができます。
4. 名前付き引数
関数を呼び出す際に、引数の名前を指定して値を渡すことができます。これは、特に引数の数が多い場合や、デフォルト引数を使っている場合にコードの可読性を高めるのに役立ちます。
“`kotlin
fun buildMessage(recipient: String, sender: String, subject: String = “件名なし”, body: String) {
println(“To: $recipient”)
println(“From: $sender”)
println(“Subject: $subject”)
println(“Body:\n$body”)
}
fun main() {
// 順番通りに渡す場合
buildMessage(“Alice”, “Bob”, “会議のお知らせ”, “明日の会議は10時からです。”)
println("---")
// 名前付き引数を使う場合 (順番を入れ替えてもOK)
buildMessage(
recipient = "Charlie",
body = "プロジェクトの進捗報告です。",
sender = "David" // デフォルト引数のsubjectは省略
)
}
“`
実行結果:
“`
To: Alice
From: Bob
Subject: 会議のお知らせ
Body:
明日の会議は10時からです。
To: Charlie
From: David
Subject: 件名なし
Body:
プロジェクトの進捗報告です。
“`
名前付き引数は、引数の意味を明確にするだけでなく、デフォルト引数を省略しつつ、その後の引数に値を渡したい場合にも便利です。
5. 単一式関数
関数本体が単一の式で表現できる場合、波括弧{}
とreturn
キーワードを省略して、イコール=
を使ってより簡潔に書くことができます。戻り値の型も、多くの場合型推論されるため省略可能です。
“`kotlin
// 通常の書き方
fun add(a: Int, b: Int): Int {
return a + b
}
// 単一式関数として書く
fun addSimplified(a: Int, b: Int): Int = a + b
// 戻り値の型も省略 (型推論される)
fun addMoreSimplified(a: Int, b: Int) = a + b // Int型が推論される
fun main() {
println(add(1, 2))
println(addSimplified(3, 4))
println(addMoreSimplified(5, 6))
}
“`
6. ローカル関数
関数の中に別の関数を定義することができます。これをローカル関数と呼びます。ローカル関数は、それを定義した関数の中からのみ呼び出すことができます。
“`kotlin
fun calculateAndPrintSum(a: Int, b: Int) {
// ローカル関数
fun sum(x: Int, y: Int): Int {
return x + y
}
val result = sum(a, b) // ローカル関数を呼び出し
println("$a + $b = $result")
}
fun main() {
calculateAndPrintSum(10, 20)
// sum(1, 2) // エラー! main関数からはローカル関数sumは呼び出せない
}
“`
ローカル関数は、親関数の中で一度だけ使われるような、特定の処理の繰り返しや分割に使われます。親関数のローカル変数にアクセスすることも可能です。
7. 拡張関数
Kotlinの非常に強力でユニークな機能の一つに「拡張関数」があります。これは、既存のクラスのソースコードを変更することなく、そのクラスに新しい関数を追加したかのように見せかけることができる機能です。
例えば、標準のString
クラスに、文字列を反転させるreverseString
という関数を追加したいとします。
“`kotlin
// StringクラスにreverseStringという拡張関数を追加
fun String.reverseString(): String {
return this.reversed() // thisは拡張されるオブジェクト(ここではString)を参照
}
fun main() {
val original = “Kotlin”
val reversed = original.reverseString() // 마치 String 클래스의 메소드인 것처럼 호출
println(“$original を反転させると $reversed”)
}
“`
実行結果:
Kotlin を反転させると niltoK
拡張関数は、レシーバ型(ここではString
)と関数名の間にピリオド.
を置いて定義します。関数本体の中では、this
キーワードを使って拡張されるオブジェクトを参照できます(省略されることも多いです)。
拡張関数は、ライブラリのクラスに便利な機能を追加したり、特定のクラスのオブジェクトに対する処理を、そのクラス自身のメソッドのように自然に記述したりするのに非常に役立ちます。
クラスとオブジェクト指向プログラミング (OOP)
Kotlinはオブジェクト指向プログラミング(OOP)をサポートしています。OOPは、プログラムを「オブジェクト」の集まりとして捉え、それぞれのオブジェクトがデータ(プロパティ)と振る舞い(メソッド)を持つという考え方です。
1. クラスの定義 (classキーワード)
オブジェクトの設計図となるのが「クラス」です。Kotlinではclass
キーワードを使ってクラスを定義します。
kotlin
class クラス名 {
// プロパティ (データ)
// メソッド (振る舞い)
}
例:
“`kotlin
class Dog {
// 犬のプロパティ (例: 名前、年齢)
var name: String = “ハチ”
var age: Int = 0
// 犬のメソッド (例: 鳴く)
fun bark() {
println("$name がワンワン鳴いています!")
}
}
“`
2. プロパティ (constructorとinitブロック)
クラスが持つデータは「プロパティ」として定義します。プロパティは、var
またはval
キーワードを使って宣言します。プロパティの初期化は、主コンストラクタ、副コンストラクタ、またはinit
ブロックで行います。
“`kotlin
class Person(val name: String, var age: Int) { // 主コンストラクタでプロパティを定義し初期化
// initブロック: オブジェクトが生成される際に実行される
init {
println("Personオブジェクトが生成されました: 名前は $name, 年齢は $age")
}
fun introduce() {
println("こんにちは、私の名前は$nameです。$age歳です。")
}
}
fun main() {
val person1 = Person(“Alice”, 30) // オブジェクト生成時に主コンストラクタに値を渡す
person1.introduce()
person1.age = 31 // varプロパティなので値を変更できる
person1.introduce()
// person1.name = “Bob” // エラー! valプロパティなので値を変更できない
}
“`
実行結果:
Personオブジェクトが生成されました: 名前は Alice, 年齢は 30
こんにちは、私の名前はAliceです。30歳です。
こんにちは、私の名前はAliceです。31歳です。
3. コンストラクタ (主コンストラクタと副コンストラクタ)
オブジェクトを生成する際に初期化を行うのがコンストラクタです。Kotlinには「主コンストラクタ」と「副コンストラクタ」があります。
-
主コンストラクタ: クラスヘッダーの一部として定義されます。プロパティの定義や初期化に最もよく使われます。
kotlin
class Person(val name: String, var age: Int) {
// 主コンストラクタのパラメータを使ってプロパティを初期化
}もし主コンストラクタにアノテーションや可視性修飾子がない場合は、
constructor
キーワードは省略できます。kotlin
class Person public @Inject constructor(name: String) { ... } // constructorキーワードが必要な例 -
副コンストラクタ:
constructor
キーワードを使ってクラス本体内で定義されます。複数の初期化方法を提供したい場合に使われます。副コンストラクタは、直接的または間接的に主コンストラクタを呼び出す必要があります(this()
キーワードを使用)。“`kotlin
class Person(val name: String) { // 主コンストラクタ
var age: Int = 0 // プロパティconstructor(name: String, age: Int) : this(name) { // 副コンストラクタ this.age = age // 主コンストラクタを呼び出し、その後ageを初期化 } fun introduce() { println("こんにちは、私の名前は$nameです。${if (age > 0) "$age歳です。" else "年齢は不明です。"} ") }
}
fun main() {
val person1 = Person(“Alice”) // 主コンストラクタを呼び出し
person1.introduce()val person2 = Person("Bob", 25) // 副コンストラクタを呼び出し person2.introduce()
}
“`
4. インスタンスの生成
クラスからオブジェクト(インスタンス)を生成するには、クラス名の後ろにカッコ()
をつけてコンストラクタを呼び出します。KotlinではJavaのようなnew
キーワードは不要です。
kotlin
val myDog = Dog() // Dogクラスのインスタンスを生成
val john = Person("John", 40) // Personクラスのインスタンスを生成
5. メソッド
クラスが持つ「振る舞い」はメソッドとして定義します。メソッドは、クラスのプロパティにアクセスしたり、外部から受け取った引数を使って特定の処理を実行したりします。
“`kotlin
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
fun subtract(a: Int, b: Int): Int {
return a - b
}
}
fun main() {
val calc = Calculator() // Calculatorのインスタンスを生成
val sum = calc.add(10, 5) // メソッドを呼び出し
val difference = calc.subtract(10, 5)
println(“合計: $sum”) // 出力: 合計: 15
println(“差: $difference”) // 出力: 差: 5
}
“`
6. 可視性修飾子 (public, private, protected, internal)
クラス、プロパティ、メソッドなどの要素が、プログラムのどこからアクセス可能かを制御するために「可視性修飾子」を使います。
public
: どこからでもアクセス可能。Kotlinではこれがデフォルトです。private
: 同じファイル内(トップレベル宣言の場合)または同じクラス/オブジェクト内からのみアクセス可能。protected
: 同じクラス/オブジェクト内、およびそのサブクラスからアクセス可能。Javaと異なり、同じパッケージからアクセスすることはできません。internal
: 同じモジュール内からアクセス可能。モジュールとは、IntelliJ IDEAのモジュールやGradleのソースセットなど、一緒にコンパイルされるKotlinコードのまとまりを指します。
“`kotlin
open class Parent { // 継承可能にするためにopenが必要
private val secret = “Private secret”
protected open val wisdom = “Protected wisdom” // サブクラスからアクセス可能
internal val moduleData = “Internal data” // 同じモジュールからアクセス可能
val publicInfo = “Public info” // デフォルトはpublic
private fun revealSecret() { // 同じクラス内からのみ呼び出し可能
println(secret)
}
fun shareWisdom() {
println(wisdom)
}
}
class Child : Parent() {
fun accessParentMembers() {
// println(secret) // エラー! privateはアクセスできない
println(wisdom) // protectedはアクセス可能
println(moduleData) // internalはアクセス可能 (同じモジュール内なら)
println(publicInfo) // publicはアクセス可能
// revealSecret() // エラー! privateメソッドはアクセスできない
}
override val wisdom: String = "Child's wisdom" // overrideで上書き
}
fun main() {
val parent = Parent()
// println(parent.secret) // エラー!
// println(parent.wisdom) // protectedは外部からアクセスできない
println(parent.moduleData) // internalは同じモジュール内ならアクセス可能
println(parent.publicInfo) // publicはアクセス可能
val child = Child()
child.accessParentMembers()
child.shareWisdom() // overrideされたChildのwisdomが出力される
}
“`
7. 継承 (openキーワード, overrideキーワード)
Kotlinでは、既存のクラス(スーパークラス、親クラス)の性質を受け継いで新しいクラス(サブクラス、子クラス)を作成することができます。これを継承と呼びます。継承により、コードの再利用や共通部分の抽象化が可能になります。
Kotlinのクラスやメソッドは、デフォルトでは継承やオーバーライド(上書き)ができません。継承可能にするにはopen
キーワードをクラスやメンバーにつける必要があります。また、スーパークラスのメンバーをサブクラスで上書きするには、サブクラス側でoverride
キーワードをつけます。
“`kotlin
open class Animal(val name: String) { // openで継承可能にする
open fun makeSound() { // openでオーバーライド可能にする
println(“$name が何かの音を立てています。”)
}
}
class Dog(name: String) : Animal(name) { // Animalクラスを継承
override fun makeSound() { // makeSoundメソッドをオーバーライド
println(“$name がワンワン鳴いています!”)
}
fun fetch() {
println("$name がボールを取ってきました!")
}
}
class Cat(name: String) : Animal(name) { // Animalクラスを継承
override fun makeSound() { // makeSoundメソッドをオーバーライド
println(“$name がニャーニャー鳴いています。”)
}
}
fun main() {
val animal: Animal = Dog(“ポチ”) // サブクラスのオブジェクトをスーパークラス型で持つ
animal.makeSound() // 実行されるのはDogのmakeSound (ポリモーフィズム)
// animal.fetch() // エラー!Animal型にはfetchメソッドがない
val dog = Dog("ハル")
dog.makeSound() // DogのmakeSound
dog.fetch() // Dog独自のfetch
val cat = Cat("ミケ")
cat.makeSound() // CatのmakeSound
}
“`
サブクラスのコンストラクタは、スーパークラスのコンストラクタを呼び出す必要があります。上記のようにクラスヘッダーで指定するか、副コンストラクタでsuper()
を使って明示的に呼び出します。
8. 抽象クラス (abstract)
「抽象クラス」は、それ自体はインスタンス化できないクラスで、他のクラスに継承されることを前提としています。抽象クラスは、実装を持つメソッドと、実装を持たない「抽象メソッド」を持つことができます。抽象メソッドを持つクラスは、必ず抽象クラスとして宣言する必要があります(abstract
キーワード)。
“`kotlin
abstract class Shape { // 抽象クラス
abstract fun area(): Double // 抽象メソッド (実装なし)
open fun printDescription() { // 具象メソッド (実装あり、オーバーライド可能)
println("これは図形です。")
}
}
class Circle(val radius: Double) : Shape() {
override fun area(): Double { // 抽象メソッドを実装 (override必須)
return Math.PI * radius * radius
}
override fun printDescription() { // 具象メソッドをオーバーライド (任意)
println("これは半径 $radius の円です。")
}
}
class Square(val side: Double) : Shape() {
override fun area(): Double { // 抽象メソッドを実装
return side * side
}
}
fun main() {
// val shape = Shape() // エラー!抽象クラスはインスタンス化できない
val circle: Shape = Circle(5.0) // サブクラスのインスタンスを抽象クラス型で持つ
println("円の面積: ${circle.area()}") // Circleのareaが呼ばれる
circle.printDescription() // CircleのprintDescriptionが呼ばれる
val square: Shape = Square(4.0)
println("四角形の面積: ${square.area()}") // Squareのareaが呼ばれる
square.printDescription() // ShapeのprintDescriptionが呼ばれる
}
“`
抽象クラスは、共通のインターフェースや部分的な実装を提供し、サブクラスに具体的な実装を強制したい場合に便利です。
9. インターフェース (interface)
インターフェースは、クラスが持つべき「機能」や「契約」を定義するものです。メソッドの宣言(抽象メソッド)や、プロパティの宣言、さらにはデフォルト実装を持つメソッドやプロパティも定義できます。インターフェースは状態(フィールドに具体的な値を保持すること)を持つことはできません。
KotlinのインターフェースはJava 8以降のインターフェースに似ており、デフォルト実装を持つことができます。
“`kotlin
interface Clickable {
fun click() // 抽象メソッド
fun showOff() { // デフォルト実装を持つメソッド
println("I'm clickable!")
}
}
interface Focusable {
fun setFocus(focused: Boolean) // 抽象メソッド
fun showOff() { // デフォルト実装を持つメソッド
println("I'm focusable!")
}
}
class Button : Clickable, Focusable { // 複数のインターフェースを実装
override fun click() { // Clickableのclickを実装
println(“Button was clicked”)
}
override fun setFocus(focused: Boolean) { // FocusableのsetFocusを実装
println("Button is ${if (focused) "focused" else "unfocused"}")
}
override fun showOff() { // 両方のインターフェースに同じ名前のデフォルト実装がある場合、どちらを呼び出すか指定するか、独自に実装する必要がある
super<Clickable>.showOff() // ClickableのshowOffを呼び出し
super<Focusable>.showOff() // FocusableのshowOffを呼び出し
println("I'm a Button!") // 独自の処理
}
}
fun main() {
val button = Button()
button.click()
button.setFocus(true)
button.showOff()
}
“`
実行結果:
Button was clicked
Button is focused
I'm clickable!
I'm focusable!
I'm a Button!
クラスは複数のインターフェースを実装できますが、抽象クラスや具象クラスは一つしか継承できません(単一継承)。インターフェースは、異なるクラス階層に属するクラス間で共通の機能を持たせたい場合に非常に有用です。
10. データクラス (data class)
Kotlinのdata class
は、データを保持するためだけに存在するクラスを簡単に定義するための機能です。data
キーワードをクラス宣言の前につけるだけで、コンパイラが以下の便利なメソッドを自動的に生成してくれます。
equals()
: オブジェクトの内容が等しいか比較hashCode()
: ハッシュコードを生成toString()
: オブジェクトの内容を表す文字列を生成copy()
: オブジェクトをコピーして、一部のプロパティを変更componentN()
: 構造分解宣言に使用 (例:val (name, age) = person
)
“`kotlin
data class User(val name: String, val age: Int)
fun main() {
val user1 = User(“Alice”, 30)
val user2 = User(“Alice”, 30)
val user3 = User(“Bob”, 25)
println(user1) // toString() が自動生成されている -> 出力: User(name=Alice, age=30)
println(user1 == user2) // equals() が自動生成されている -> 出力: true
println(user1 == user3) // 出力: false
val user4 = user1.copy() // copy() を使ってコピー
val user5 = user1.copy(age = 31) // 一部のプロパティを変更してコピー
println(user4) // 出力: User(name=Alice, age=30)
println(user5) // 出力: User(name=Alice, age=31)
val (name, age) = user1 // 構造分解宣言
println("名前: $name, 年齢: $age") // 出力: 名前: Alice, 年齢: 30
}
“`
data class
を使うと、Javaで同様のクラスを作成する際に必要だったボイラープレートコード(定型句的なコード)を大幅に削減できます。
11. シングルトンオブジェクト (object declaration)
特定のクラスのインスタンスがアプリケーション全体で一つだけ存在することを保証したい場合があります(シングルトンパターン)。Kotlinでは、object
キーワードを使うことで、シングルトンオブジェクトを簡単に宣言できます。
“`kotlin
object DatabaseManager { // objectキーワードでシングルトンを宣言
private var connectionCount = 0
fun connect() {
connectionCount++
println("データベースに接続しました。現在の接続数: $connectionCount")
}
fun disconnect() {
if (connectionCount > 0) {
connectionCount--
println("データベースから切断しました。現在の接続数: $connectionCount")
} else {
println("既に切断されています。")
}
}
fun getConnectionCount(): Int {
return connectionCount
}
}
fun main() {
DatabaseManager.connect() // クラス名.メソッド名 で直接アクセス
DatabaseManager.connect()
println(“現在の接続数: ${DatabaseManager.getConnectionCount()}”)
DatabaseManager.disconnect()
DatabaseManager.disconnect()
DatabaseManager.disconnect() // 接続されていない場合の処理も実行される
}
“`
実行結果:
データベースに接続しました。現在の接続数: 1
データベースに接続しました。現在の接続数: 2
現在の接続数: 2
データベースから切断しました。現在の接続数: 1
データベースから切断しました。現在の接続数: 0
既に切断されています。
object
宣言によって作られたオブジェクトは、その名前(例: DatabaseManager
)を使って、クラスの静的メンバーにアクセスするように直接アクセスできます。遅延初期化され、スレッドセーフであることが保証されます。
12. コンパニオンオブジェクト (companion object)
クラスの中に、そのクラスのインスタンスではなく、クラス自体に関連付けられたメンバー(Javaでいうstaticメンバー)を定義したい場合があります。Kotlinでは、companion object
を使うことでこれを実現できます。
“`kotlin
class MyClass {
// コンパニオンオブジェクト
companion object {
private const val TAG = “MyClass” // 定数
fun create(): MyClass { // ファクトリメソッド
println("$TAG: create() called")
return MyClass()
}
}
fun doSomething() {
println("$TAG: doSomething() called")
}
}
fun main() {
// コンパニオンオブジェクトのメンバーは、クラス名.メンバー名 でアクセス
val instance = MyClass.create()
instance.doSomething()
// Java側から呼び出す場合は @JvmStatic アノテーションが必要になる場合がある
}
“`
コンパニオンオブジェクトのメンバーは、クラス名に続けてドット.
を使ってアクセスできます。コンパニオンオブジェクトは、内部的にはクラス内にネストされたシングルトンオブジェクトとして実装されます。ファクトリメソッド(オブジェクト生成をカプセル化するメソッド)や定数の定義によく使われます。
13. enumクラス (enum class)
列挙型は、定義済みの限られた値のセットを表現するために使われます。Kotlinではenum class
キーワードを使って列挙型を定義します。各列挙定数は、プロパティやメソッドを持つことができます。
“`kotlin
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
enum class Color(val rgb: Int) { // プロパティを持つ列挙型
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF); // 定義リストの終端にはセミコロンが必要な場合がある
fun printColor() { // メソッドを持つ列挙型
println("Color: $name, RGB: ${Integer.toHexString(rgb)}")
}
}
fun main() {
val direction = Direction.NORTH
println(direction) // 出力: NORTH
println(direction.name) // 出力: NORTH
println(direction.ordinal) // 定義順のインデックス -> 出力: 0
val color = Color.GREEN
println(color.rgb) // 出力: 65280 (0x00FF00の10進数)
color.printColor() // 出力: Color: GREEN, RGB: ff00
// when式との相性が良い
when (direction) {
Direction.NORTH -> println("北へ")
Direction.SOUTH -> println("南へ")
// NORTHとSOUTH以外のケースを網羅しないと、elseブランチが必要になる
else -> println("東か西へ")
}
}
“`
enum class
は、固定された選択肢を扱う場合に、コードの可読性と安全性を高めます。
14. sealedクラス (sealed class)
「シールクラス」は、継承できるクラスを、そのクラスが定義されているファイル(またはモジュール)内の、特定のサブクラスに限定するためのクラスです。これにより、when
式でシールクラスのすべてのサブクラスを網羅した場合に、else
ブランチを不要にすることができます。
“`kotlin
sealed class Result { // sealedキーワードでシールクラスを宣言
data class Success(val data: String) : Result() // サブクラスはResultを継承
data class Error(val message: String, val code: Int) : Result() // サブクラスは同じファイル内にある必要あり
object Loading : Result() // object宣言もサブクラスとして定義できる
}
fun handleResult(result: Result) {
when (result) { // シールクラスなので、全てのサブクラスをwhen式で網羅するとelse不要
is Result.Success -> println(“成功: ${result.data}”)
is Result.Error -> println(“エラー: ${result.message} (コード: ${result.code})”)
Result.Loading -> println(“読み込み中…”) // objectはインスタンスが一つなので型チェックisは不要
}
}
fun main() {
handleResult(Result.Loading)
handleResult(Result.Success(“データ取得完了”))
handleResult(Result.Error(“ネットワークエラー”, 500))
// 新しいサブクラスをResultクラスの定義ファイルに追加しない限り、
// handleResult関数は安全に変更できる
}
“`
シールクラスは、取りうる状態が限られているオブジェクトを表現する場合に非常に便利です。例えば、非同期処理の状態(成功、失敗、読み込み中)などを表現するのに適しています。
より進んだKotlinの機能
Kotlinには、コードをより安全に、より簡潔に書くための便利な機能がたくさんあります。
1. Null安全
KotlinのNull安全は、変数がnull
になりうるかどうかを型システムで区別することで、NullPointerExceptionの発生を防ぐための仕組みです。
- NonNull型: デフォルト。
null
を代入できない。 - Nullable型 (
?
): 型名の後ろに?
をつける。null
を代入できる。
Nullable型の変数は、そのままではメソッド呼び出しやプロパティへのアクセスができません。null
である可能性があるため、NullPointerExceptionを起こさないための特別な扱いが必要です。
KotlinはNullable型を安全に扱うためのいくつかの演算子を提供しています。
-
セーフコール演算子 (
?.
): オブジェクトがnull
でない場合のみ、メソッド呼び出しやプロパティアクセスを実行します。オブジェクトがnull
の場合は、式全体の結果がnull
になります。“`kotlin
var name: String? = “Kotlin”
println(name?.length) // nameがnullでないので length を呼び出す -> 6name = null
println(name?.length) // nameがnullなので length は呼び出されず null を返す -> null
“` -
エルビス演算子 (
?:
):?.
と組み合わせて使うことが多いです。?.
の左辺がnull
でない場合はその結果を、null
の場合は右辺の値を返します。デフォルト値を提供したい場合に便利です。“`kotlin
var name: String? = null
val nameLength = name?.length ?: 0 // nameがnullなので 0 を返す
println(nameLength) // 出力: 0name = “Kotlin”
val nameLength2 = name?.length ?: 0 // nameがnullでないので name.length の結果 (6) を返す
println(nameLength2) // 出力: 6
“` -
非Nullアサーション演算子 (
!!
): Nullable型の変数をNonNull型として扱いたい場合に強制的にNullチェックを行います。ただし、変数がnull
だった場合はNullPointerExceptionが発生します。これはKotlinのNull安全の仕組みを「回避」するものであり、NullPointerExceptionが発生する可能性を認識している場合にのみ使うべきです。安易な使用は避けるべきです。“`kotlin
var name: String? = “Kotlin”
println(name!!.length) // nameはnullでないのでOK -> 6name = null
// println(name!!.length) // 実行時エラー! NullPointerExceptionが発生
“` -
セーフキャスト演算子 (
as?
): オブジェクトを指定した型にキャストしようとします。キャストに成功した場合はその型のオブジェクトを、失敗した場合はnull
を返します。キャストできない場合に例外を発生させるas
演算子よりも安全です。“`kotlin
val value: Any = “Hello”
val stringValue: String? = value as? String // キャスト成功、”Hello”が代入される
val intValue: Int? = value as? Int // キャスト失敗、nullが代入されるprintln(stringValue) // 出力: Hello
println(intValue) // 出力: null
“`
Null安全はKotlinの最も重要な特徴の一つであり、NullPointerExceptionによるバグを大幅に減らすのに貢献します。Nullable型を扱う際は、これらの安全な演算子を活用することが重要です。
2. スマートキャスト
Kotlinコンパイラは、変数の型をチェックした後に、その変数に対して安全にその型のメンバーにアクセスできることを自動的に推論します。これをスマートキャストと呼びます。特に、is
演算子や!= null
チェックの後によく起こります。
“`kotlin
fun printLength(obj: Any) {
if (obj is String) {
// objがString型であることが保証されるので、Stringのメンバーに直接アクセスできる
// ここでobjは自動的にString型にスマートキャストされる
println(“文字列の長さ: ${obj.length}”)
} else if (obj is Int) {
// objがInt型であることが保証される
// ここでobjは自動的にInt型にスマートキャストされる
println(“整数の値: $obj”)
} else {
println(“不明な型です”)
}
}
fun main() {
printLength(“Kotlin”) // 出力: 文字列の長さ: 6
printLength(123) // 出力: 整数の値: 123
printLength(3.14) // 出力: 不明な型です
}
“`
Nullable型の場合も同様に機能します。
“`kotlin
fun printNonNullStringLength(text: String?) {
if (text != null) {
// textがnullでないことが保証されるので、Stringのメンバーに直接アクセスできる
// ここでtextは自動的にNonNull String型にスマートキャストされる
println(“文字列の長さ: ${text.length}”)
}
}
fun main() {
printNonNullStringLength(“NonNull String”) // 出力: 文字列の長さ: 14
printNonNullStringLength(null) // 何も出力されない
}
“`
スマートキャストにより、不要なキャスト操作が減り、コードがより簡潔になります。
3. コレクション (List, Set, Map)
プログラムでは、複数のデータをまとめて扱うことがよくあります。Kotlinの標準ライブラリは、コレクションを扱うための強力な機能を提供しています。主なコレクションの種類は以下の通りです。
- List: 順序付けられた要素の集まり。同じ要素を複数含めることができます。
- Set: 重複しない要素の集まり。順序は保証されません(実装による)。
- Map: キーと値のペアの集まり。キーは重複せず、各キーは一つの値に関連付けられます。
Kotlinのコレクションは、デフォルトで「不変(Immutable)」です。一度作成したら、要素の追加、削除、変更はできません。要素を変更できる「可変(Mutable)」なコレクションも用意されています。
-
不変コレクションの作成:
- リスト:
listOf()
- セット:
setOf()
- マップ:
mapOf()
- リスト:
-
可変コレクションの作成:
- リスト:
mutableListOf()
,ArrayList()
- セット:
mutableSetOf()
,HashSet()
- マップ:
mutableMapOf()
,HashMap()
- リスト:
例:
“`kotlin
fun main() {
// 不変リスト
val immutableList = listOf(“Apple”, “Banana”, “Cherry”)
// immutableList[0] = “Orange” // エラー! 不変リストは変更できない
println(immutableList[1]) // 出力: Banana
println(immutableList.size) // 出力: 3
// 可変リスト
val mutableList = mutableListOf("Apple", "Banana")
mutableList.add("Cherry") // 要素を追加できる
mutableList[0] = "Orange" // 要素を変更できる
println(mutableList) // 出力: [Orange, Banana, Cherry]
// 不変セット
val immutableSet = setOf("A", "B", "A") // 重複は無視される
println(immutableSet) // 出力: [A, B] (順序は不定)
// 可変セット
val mutableSet = mutableSetOf("X", "Y")
mutableSet.add("Z")
mutableSet.remove("X")
println(mutableSet) // 出力: [Y, Z] (順序は不定)
// 不変マップ
val immutableMap = mapOf("one" to 1, "two" to 2, "three" to 3)
println(immutableMap["one"]) // キーを指定して値を取得 -> 出力: 1
println(immutableMap.keys) // キーの集合 -> 出力: [one, two, three]
println(immutableMap.values) // 値の集合 -> 出力: [1, 2, 3]
// 可変マップ
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3 // 要素を追加または変更
mutableMap.remove("a") // 要素を削除
println(mutableMap) // 出力: {b=2, c=3}
}
“`
不変コレクションは、マルチスレッド環境で安全であるという利点があります。データの変更が不要な場合は、不変コレクションを使うことを推奨します。
4. コレクション操作 (filter, map, forEachなど)
Kotlinのコレクションライブラリには、リストの変換やフィルタリングなど、様々な操作を行うための便利な関数が豊富に用意されています。これらの関数は、関数型プログラミングのスタイルでコレクションを処理するのに役立ちます。よく使う操作関数の一部を紹介します。
-
filter
: 条件を満たす要素のみを残した新しいリストを作成します。kotlin
val numbers = listOf(1, 2, 3, 4, 5, 6)
val evenNumbers = numbers.filter { it % 2 == 0 } // ラムダ式を使って条件を指定
println(evenNumbers) // 出力: [2, 4, 6] -
map
: 各要素に変換処理を適用した新しいリストを作成します。kotlin
val names = listOf("Alice", "Bob", "Charlie")
val lengths = names.map { it.length } // 各文字列の長さを新しいリストにする
println(lengths) // 出力: [5, 3, 7] -
forEach
: 各要素に対して処理を実行します(新しいコレクションは作成しません)。kotlin
val fruits = listOf("Apple", "Banana", "Cherry")
fruits.forEach { println("Fruit: $it") } // 各要素をprintlnで表示 -
first
,last
: 最初または最後の要素を取得します(コレクションが空の場合は例外)。firstOrNull
,lastOrNull
は空の場合はnull
を返します。 find
: 条件を満たす最初の要素を取得します(見つからない場合はnull
)。any
: 条件を満たす要素が一つでも存在するかチェックします。all
: 全ての要素が条件を満たすかチェックします。count
: 条件を満たす要素の数を数えます。groupBy
: 指定したキーによって要素をグループ化し、Mapを返します。sorted
,sortedBy
: 要素をソートします。
これらの関数は、連鎖させて使うことで、複数の操作を効率的に記述できます(例: list.filter { ... }.map { ... }.forEach { ... }
)。
5. ラムダ式と高階関数
Kotlinでは、関数を値として扱うことができます。つまり、関数を変数に代入したり、関数の引数として別の関数を渡したり、関数から関数を返したりすることができます。このような、関数を引数に取ったり関数を返したりする関数を「高階関数」と呼びます。
関数を値として渡す際に便利なのが「ラムダ式」です。ラムダ式は、名前を持たない関数リテラルです。
“`kotlin
// ラムダ式の基本形 { パラメータ -> 関数本体 }
val sum = { a: Int, b: Int -> a + b } // Intを2つ受け取り、Intを返すラムダ式
println(sum(3, 5)) // ラムダ式を呼び出す -> 出力: 8
// 高階関数の例: operateNumbers関数は、2つのIntと、それらを処理する関数を受け取る
fun operateNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b) // 受け取ったoperation関数を実行
}
fun main() {
// operateNumbers関数に、sumラムダ式を渡す
val result = operateNumbers(10, 20, sum)
println(“Result: $result”) // 出力: Result: 30
// operateNumbers関数に、直接ラムダ式を記述して渡す
val result2 = operateNumbers(10, 20, { x, y -> x * y })
println("Result 2: $result2") // 出力: Result 2: 200
// ラムダ式が関数の最後の引数である場合、カッコの外に出して書ける (Trailing lambda)
val result3 = operateNumbers(10, 20) { x, y -> x - y }
println("Result 3: $result3") // 出力: Result 3: -10
// ラムダ式のパラメータが1つだけの場合、it という名前で参照できる
val numbers = listOf(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map { it * it } // itは各要素 (Int) を参照
println(squaredNumbers) // 出力: [1, 4, 9, 16, 25]
}
“`
ラムダ式と高階関数は、コレクション操作の関数(filter
, map
など)で頻繁に利用されます。これにより、宣言的で読みやすいコードを書くことができます。
6. 型エイリアス (typealias)
長い型名や複雑な関数型に別名をつけたい場合にtypealias
を使います。コードの可読性を向上させることができます。新しい型を定義するわけではなく、既存の型に別名をつけるだけです。
“`kotlin
typealias EmployeeId = String // String型にEmployeeIdという別名をつける
typealias OnClickListener = (View) -> Unit // (View)を引数に取り、Unitを返す関数型に別名をつける
class View {
fun setOnClickListener(listener: OnClickListener) {
// … リスナーを設定する処理
}
}
fun main() {
val id: EmployeeId = “EMP001”
println(id)
val button = View()
button.setOnClickListener { view ->
println("ボタンがクリックされました")
}
// ラムダ式が最後の引数かつパラメータが一つなので、it を使ってさらに簡潔に書ける
button.setOnClickListener { println("ボタンがクリックされました") }
}
“`
7. 委譲 (Delegation – byキーワード)
Kotlinでは、継承を使わずに「委譲」を使ってコードの再利用を実現できます。あるインターフェースの実装を、別のオブジェクトに任せることができます。by
キーワードを使って簡単に委譲を実装できます。
“`kotlin
interface Printer {
fun print(message: String)
}
class ConsolePrinter : Printer {
override fun print(message: String) {
println(“コンソール出力: $message”)
}
}
class DelegatePrinter(printer: Printer) : Printer by printer {
// DelegatePrinter は Printer インターフェースを実装しているように見えるが、
// 実際のprint()の処理は printer オブジェクトに委譲される。
// 必要に応じて、print() メソッドをオーバーライドして独自の処理を追加することもできる。
}
fun main() {
val console = ConsolePrinter()
val delegated = DelegatePrinter(console)
delegated.print("委譲された印刷メッセージ") // ConsolePrinterのprintメソッドが実行される
}
“`
8. プロパティ委譲 (Delegated Properties – lazy, observableなど)
プロパティのgetter/setterのロジックを、特定のオブジェクトに委譲する仕組みです。標準ライブラリにはいくつかの委譲オブジェクトが提供されています。
lazy
: プロパティへの最初のアクセス時に遅延して初期化します。observable
: プロパティの値が変更された際に通知を受け取ります。vetoable
: プロパティの値が変更される前に介入し、変更を許可または拒否します。
“`kotlin
import kotlin.properties.Delegates
class Example {
// lazy委譲: 最初に使用されるまで初期化されない
val lazyValue: String by lazy {
println(“lazyValueを初期化します”)
“Hello Lazy”
}
// observable委譲: 値が変更された際に呼ばれるリスナーを持つ
var name: String by Delegates.observable("初期値") {
prop, old, new ->
println("$prop の値が $old から $new に変更されました")
}
// vetoable委譲: 値が変更される前に呼ばれるリスナーを持ち、変更を許可/拒否できる
var age: Int by Delegates.vetoable(0) {
prop, old, new ->
println("$prop の値を $old から $new に変更しようとしています")
new >= 0 // 0以上の値のみ許可
}
}
fun main() {
val e = Example()
println(e.lazyValue) // lazyValueが初めてアクセスされ、初期化される
println(e.lazyValue) // 2回目以降は初期化は実行されない
e.name = "Kotlin" // nameプロパティが変更され、リスナーが呼ばれる
e.name = "World" // 再度変更
e.age = 20 // ageプロパティが変更され、リスナーが呼ばれ、許可される
println("Age: ${e.age}")
e.age = -5 // ageプロパティが変更され、リスナーが呼ばれ、拒否される
println("Age: ${e.age}") // 値は変更されない
}
“`
実行結果:
lazyValueを初期化します
Hello Lazy
Hello Lazy
property name の値が 初期値 から Kotlin に変更されました
property name の値が Kotlin から World に変更されました
property age の値を 0 から 20 に変更しようとしています
Age: 20
property age の値を 20 から -5 に変更しようとしています
Age: 20
プロパティ委譲は、プロパティの共通的な挙動をカプセル化し、コードの重複を避けるのに役立ちます。
コルーチン (簡単な紹介)
非同期処理や並行処理を記述する際に、Kotlinでは「コルーチン」が推奨されています。コルーチンは、OSスレッドよりもはるかに軽量な処理単位であり、ノンブロッキングなコードをより手続き的なスタイルで記述することを可能にします。
コルーチンは初心者にとって少し高度なトピックですが、その基本的な概念と最も簡単な使い方を少しだけ紹介します。
コルーチンとは?
コルーチンは、処理の途中で一時停止(サスペンド)し、後で再開できる計算インスタンスです。これにより、完了を待っている間にスレッドをブロックすることなく、時間のかかる処理(ネットワーク通信、ファイル読み込みなど)を実行できます。
なぜコルーチンが必要か?
時間のかかる処理をメインスレッド(UIスレッドなど)で実行すると、アプリケーションが応答しなくなり固まってしまいます。これを避けるために、伝統的にはコールバックやFutures/Promisesなどを使った非同期処理が使われてきましたが、コードが複雑になりがちです(コールバック地獄など)。コルーチンは、非同期処理をあたかも同期的なコードのように書けるため、コードの可読性と保守性を向上させます。
基本的な使い方(ごく簡単な例)
コルーチンを使うには、コルーチンライブラリをプロジェクトに追加する必要があります。IntelliJ IDEAでプロジェクトを作成する際に適切な設定を選んでいれば既に追加されているかもしれません。Gradleプロジェクトの場合はbuild.gradle.kts
に依存関係を追加します。
kotlin
// build.gradle.kts (または build.gradle) に追加
// dependencies {
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") // バージョンは適宜最新のものに
// }
最も簡単なコルーチンの実行方法は、runBlocking
というコルーチンビルダーを使う方法です。これは現在のスレッドをブロックしてコルーチンを実行するので、主にメイン関数など、他の処理をブロックしても問題ない場所でテスト目的などに使われます。実際のアプリケーションではlaunch
やasync
といったノンブロッキングなビルダーを使います。
“`kotlin
import kotlinx.coroutines.* // コルーチン関連の機能をインポート
fun main() = runBlocking { // main関数をコルーチン化 (runBlockingはスレッドをブロックする)
println(“メインスレッドの開始”)
// launch: 新しいコルーチンを起動し、結果を待たずに実行を続ける
launch { // デフォルトのディスパッチャー (多くの場合はスレッドプール) で実行
delay(1000L) // 1秒待機 (非ブロッキングな待機)
println("コルーチン1の実行")
}
launch {
delay(500L) // 0.5秒待機
println("コルーチン2の実行")
}
println("メインスレッドの終了 (コルーチン完了を待機)")
// runBlocking ブロックの終了まで待機するので、launchで起動したコルーチンが完了するのを待つ
}
“`
実行結果:
メインスレッドの開始
メインスレッドの終了 (コルーチン完了を待機)
コルーチン2の実行
コルーチン1の実行
注目すべきは、"メインスレッドの終了..."
というメッセージが、コルーチン内のメッセージより先に表示されている点です。これは、launch
が非同期で処理を開始し、runBlocking
がその完了を待っている間に、メインスレッドの次の行が実行されていることを示しています。delay
はスレッドをブロックせず、コルーチンだけをサスペンド(一時停止)させます。
コルーチンは奥が深いですが、非同期処理を扱う上での強力なツールであり、特にAndroid開発などで頻繁に利用されています。本格的にコルーチンを学ぶ際は、公式ドキュメントや専用の学習リソースを参照することをお勧めします。
Kotlinの応用分野
Kotlinは非常に汎用性の高い言語であり、様々な分野で利用されています。
- Android開発: GoogleによってAndroidアプリ開発の公式言語として推奨されており、最も主要な用途の一つです。KotlinはJavaよりも簡潔かつ安全にAndroidアプリを開発できます。Jetpack Composeのような新しいUIツールキットもKotlinファーストで設計されています。
- サーバーサイド開発: Spring Boot (Javaフレームワーク) がKotlinを公式にサポートしているほか、KotlinネイティブのサーバーサイドフレームワークであるKtorなども登場しています。JVM上で動作するため、既存のJavaライブラリ資産を活用できるのが強みです。
- デスクトップ開発: JetBrainsが提供するJetpack Compose for Desktopを使うことで、Kotlinでクロスプラットフォームのデスクトップアプリケーションを開発できます。
- マルチプラットフォーム開発: Kotlin Multiplatform Mobile (KMM) やCompose Multiplatformといった技術を使うことで、iOSとAndroidで共通のビジネスロジックやUIをKotlinで記述し、プラットフォーム固有の部分だけをそれぞれの言語で実装するといった開発スタイルが可能になっています。
- フロントエンド開発: Kotlin/JSとして、KotlinコードをJavaScriptにコンパイルし、ウェブブラウザ上で実行することも可能です。
これらの分野以外にも、データサイエンスやコマンドラインツールなど、様々な用途でKotlinが使われ始めています。
学習リソース
この記事でKotlinの基本的な文法や主要な機能を学びましたが、さらに深く学習を進めるためのリソースを紹介します。
- Kotlin公式ドキュメント: https://kotlinlang.org/docs/
最も正確で詳細な情報源です。初心者向けの「Get started」から、各機能の詳細な説明まで網羅されています。 - Kotlin Koans: https://play.kotlinlang.org/koans/
Kotlinの機能をインタラクティブな演習形式で学べるウェブサイトです。実際にコードを書きながら学ぶのに最適です。 - 書籍: Kotlin入門に関する様々な書籍が出版されています。ご自身の学習スタイルに合ったものを選んでみてください。
- オンラインコース: Udemy、Coursera、Udacityなど、様々なプラットフォームでKotlinに関するオンラインコースが提供されています。動画を見ながら体系的に学びたい場合に適しています。
- Kotlinコミュニティ: 世界中にKotlin開発者のコミュニティがあります。Stack Overflowで質問したり、Kotlin Slackチャンネルに参加したりすることで、他の開発者と交流し、助けを得ることができます。
継続的に学習し、実際にコードを書くことが、Kotlinを習得する上で最も重要です。
まとめ
この記事では、Kotlinの基本的な構文から、関数、クラスとオブジェクト指向、Null安全、コレクション、ラムダ式、そして簡単なコルーチンまで、初心者向けに詳細に解説しました。
Kotlinは、簡潔で読みやすいコード、強力なNull安全、Javaとの高い相互運用性など、多くの魅力を持つモダンな言語です。これらの特徴により、開発効率が向上し、より安全でメンテナンスしやすいプログラムを作成できます。
この記事を読んだあなたは、Kotlinプログラミングの基本的な知識を身につけました。次は、ぜひ実際にコードを書いてみてください。簡単なプログラムから始めて、少しずつ複雑なものに挑戦していくのが良いでしょう。Androidアプリ開発やサーバーサイド開発など、興味のある分野のチュートリアルを試してみるのも素晴らしいステップです。
Kotlinを学ぶ道のりは始まったばかりですが、その旅はきっとエキサイティングで価値のあるものになるはずです。Kotlinの世界を楽しんでください!応援しています!