Kotlinの Extension(拡張機能) は、単なる「便利機能」ではありません。
設計・可読性・Java資産との共存に強く影響する、Kotlinの思想そのものと言える機能です。
本記事では、基礎 → 仕組み → 実務での使い方 → 注意点 → 設計判断という流れで、誤解の余地がない形で解説します。
目次
KotlinのExtensionとは何か
定義
Extensionとは、
既存のクラスを修正せず、継承せずに、新しい関数やプロパティを「追加したように見せる」仕組み
です。
重要な前提
- クラスの実装は一切変更されない
- バイナリ互換性を壊さない
- Javaのクラス・Android SDK・外部ライブラリにも適用できる
拡張関数(Extension Function)
基本構文
fun レシーバ型.関数名(引数): 戻り値 {
// 処理
}
例:Stringに関数を追加する
fun String.lastChar(): Char {
return this[this.length - 1]
}
使用例
val name = "Kotlin"
println(name.lastChar()) // n
ポイント
thisは拡張対象のオブジェクト- 呼び出し側からは メンバー関数のように見える
拡張プロパティ(Extension Property)
基本構文
val レシーバ型.プロパティ名: 型
get() = ...
例
val String.isLong: Boolean
get() = this.length > 10
println("HelloWorld".isLong) // false
制約(重要)
- 拡張プロパティは バッキングフィールドを持てない
- 必ず
get()/set()の計算プロパティになる
Extensionの正体(必ず理解すべき核心)
Extensionは「本当にクラスを拡張している」わけではない
Extensionは 静的に解決される 構文糖衣です。
実体としては、次のようなトップレベル関数に変換されて呼び出されます。
fun foo(str: String)
静的解決の例
open class Parent
class Child : Parent()
fun Parent.type() = "parent"
fun Child.type() = "child"
val p: Parent = Child()
println(p.type()) // parent
なぜ?
- 実体の型(Child)ではなく
- 変数の宣言型(Parent) で解決されるため
Extensionでは ポリモーフィズムは起きない
メンバー関数とExtensionが競合した場
結論
メンバー関数が常に優先され、Extensionは呼ばれない
class A {
fun foo() = "member"
}
fun A.foo() = "extension"
println(A().foo()) // member
これは「Extensionはクラスの外から見える補助関数」であり、既存の振る舞いを上書きする仕組みではない ことを示しています。
アクセス修飾子の制限
Extensionは次の制約を持ちます。
- ❌
privateメンバーにはアクセスできない - ❌
protectedメンバーにもアクセスできない - ✅
public/internalのみ利用可能(条件あり)
「クラスの内部に入り込む」ことはできない
あくまで 外部APIとして見えている部分だけ を使う
Nullable Receiver(nullableな拡張)
null安全なExtension
fun String?.isNullOrBlankEx(): Boolean {
return this == null || this.isBlank()
}
val s: String? = null
println(s.isNullOrBlankEx()) // true
thisがnullになり得る- Utility関数よりも読みやすい形で表現できる
※ 実務では標準関数 isNullOrBlank() が既に存在する点には注意
よく使われる実践的なExtension例
数値・文字列ユーティリティ
fun Int.isEven(): Boolean = this % 2 == 0
10.isEven() // true
先頭文字を大文字化(安全な例)
fun String.capitalizeFirst(): String =
replaceFirstChar {
if (it.isLowerCase()) it.titlecase() else it.toString()
}
Androidでの利用(例)
fun View.visible() {
visibility = View.VISIBLE
}
fun View.gone() {
visibility = View.GONE
}
※ 実際には Android KTX に類似の拡張が既にあるため、既存拡張の有無を確認してから定義する のが望ましい
Companion Objectへの拡張
Factoryメソッド風の使い方
class User(val name: String) {
companion object
}
fun User.Companion.create(name: String): User =
User(name)
val user = User.create("Taro")
- Javaの static メソッドに近い表現
- Factory / DSL と相性が良い
importとスコープの重要性
同名Extensionは import で解決される
import com.example.ext.foo
import com.sample.ext.foo as sampleFoo
- Extensionは スコープ依存
- 「どこで定義されているか」が可読性に直結する
無秩序に増やすと、保守性が急激に下がる
設計上の指針
使うべきケース
- 表現力の向上
- ドメインを自然言語に近づけたいとき
- 状態を持たない補助的ロジック
fun Order.isCancelable(): Boolean
避けるべきケース
- 重要なビジネスロジック
- 状態変更を伴う処理
- UseCase / Service に置くべき責務
// 危険になりやすい
fun User.changePassword()
継承・Interface・Extensionの使い分け
| 手法 | 目的 |
|---|---|
| Extension | 表現力・利便性の向上 |
| 継承 | 振る舞いの差し替え |
| Interface | 契約・ポリモーフィズム |
Extensionは「設計を簡単にするための補助線」
まとめ
KotlinのExtensionは、
- クラスを汚さず
- Java資産と自然に共存し
- 可読性を飛躍的に高める
一方で、
- 静的解決
- スコープ依存
- オーバーライド不可
という明確な制約があります。
以上、KotlinのExtensionについてでした。
最後までお読みいただき、ありがとうございました。










