KotlinのExtensionについて

採用はこちら

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
  • thisnull になり得る
  • 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についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次