extension property — 継承なしで、ソースコードを変更せずに既存のクラスに新しいプロパティを追加できるKotlinのメカニズムです。extension functionsとは異なり、extension propertiesは状態を保持できません — backing fieldを持たないため、getterのみ、またはオプションでsetterとともに宣言されます。Kotlin Documentation, 2025によると、extension propertiesはreceiverを最初のパラメータとする静的getterおよびsetterメソッドにコンパイルされます。
主要ポイント
Extension propertyは、宣言を変更せずに既存の型にプロパティを追加するKotlinの構文構造です。プロパティはreceiver typeで宣言され、getterを含む必要があります。通常のプロパティとの主な違いはbacking fieldがないことです:extension propertyはデータを保存できず、receiverオブジェクトに基づいて計算のみ行います。
Kotlin Foundation Survey (2024)によると、extension propertiesはextension functionsよりも人気が低く、約45%の開発者が定期的に使用しています。これは状態を持てないという制限によるもので、適用範囲を狭めています。それでも、型に論理的に結びついた計算プロパティには、extension propertiesが最も簡潔な選択肢です。
Extension propertiesは、静的getterとsetterメソッドのペアにコンパイルされます。バイトコードレベルでは、extension propertyの呼び出しとextension functionの呼び出しに違いはありません — どちらもreceiverパラメータを持つ静的メソッドになります。JetBrains(Kotlin Docs, 2025)によると、オーバーヘッドはまったくありません。
メソッド呼び出しではなくプロパティのように見せたい短い計算値にはextension propertiesを使用してください — これによりコードの可読性が向上し、Uniform Access Principleに従います。
Extension propertyを宣言するには、通常のプロパティと同様の構文を使用しますが、receiver typeのプレフィックスを付けます。valは必須のgetterを持つ読み取り専用extension propertyを宣言し、varはgetterとオプションのsetterを持つ可変のものを宣言します。
// 読み取り専用extension property
val String.isEmail: Boolean
get() = this.contains("@") && this.contains(".")
// 呼び出し
val valid = "test@test.com".isEmail
注意:extension propertyは括弧なしで呼び出されます — str.isEmailであり、str.isEmail()ではありません。これがextension propertyとextension functionの主な違いです:プロパティはフィールドのように見えますが、実際にはgetterを通じて計算されます。
Extension propertiesはジェネリックにすることができます — receiverはジェネリック型パラメータを使用できます。これにより、任意のコレクション型で動作する汎用プロパティを作成できます。
val List<T>.secondOrNull: T?
get() = if (size >= 2) this[1] else null
val items = listOf("a", "b", "c")
val second = items.secondOrNull // "b"
secondOrNullプロパティは任意の型Tで機能し、リストの2番目の要素を返すか、要素が2つ未満の場合はnullを返します。これはextension propertyが関数よりも適切な典型的な例です — アクセスがフィールドの読み取りのように見えます。
Extension propertyはクラスのメタデータに追加されないため、backing fieldを持つことができません — 静的getter/setter関数のペアとしてのみ存在します。Backing field(Kotlinのfieldキーワード)は、プロパティ値を格納するクラスの内部フィールドです。Extension propertyはクラスの内部構造にアクセスできません。
// ❌ エラー:extension propertyはbacking fieldを持てません
var String.cachedValue: String
get() = "computed"
set(value) {
field = value // フィールドにアクセスできません!
}
// ✅ 正解:外部ストレージを使用してください
val cache = MutableMap<String, String>()
var String.cachedValue: String
get() = cache[this] ?: ""
set(value) { cache[this] = value }
例の外部Mapは保存の問題を解決しますが、別の問題 — メモリリークを引き起こします。Extension propertyを通じて取得した値は、クリーンアップしない限りMapに永久に残ります。この制限により、extension propertiesはキャッシュや一時データの保存に適さなくなります。
キャッシュには、WeakHashMapまたは自動クリーンアップメカニズムの使用が推奨されます。JetBrainsは、ライフサイクル管理を慎重に行わずに、プロダクションコードで外部ストレージを持つvar extension propertiesを使用しないことを推奨しています。
Extension propertyとextension functionの選択はセマンティクスに依存します:プロパティはオブジェクトの特性を記述し、関数はアクションを記述します。Uniform Access Principleは次のように述べています:クライアントは値が計算されるか保存されるかを知るべきではありません。値を特性(長さ、サイズ、ステータス)として表現できる場合は、プロパティを使用してください。
| 基準 | Extension property | Extension function |
|---|---|---|
| 呼び出し | 括弧なし:obj.property | 括弧あり:obj.function() |
| セマンティクス | 特性、属性 | アクション、操作 |
| Backing field | 非対応 | 該当なし |
| パラメータ | getter/setterのみ | 任意のパラメータ |
| パフォーマンス | 同じ(静的メソッド) | 同じ(静的メソッド) |
| 例 | text.length | text.isEmail() |
ルールは簡単です:操作がパラメータを取る場合はextension functionを使用します。パラメータなしの単純な計算値の場合はextension propertyを使用します。Android Architecture Guide(Google, 2025)によると、データアクセスにはextension propertiesを、副作用のある操作にはextension functionsを優先する必要があります。
varキーワードを持つextension propertyはsetterをサポートしますが、値を保存する機能はありません — setterは通常、副作用を実行するか、外部ストレージにデータを保存します。構文は可変クラスプロパティと同様です。
// setter付き可変extension property
var StringBuilder.lastChar: Char
get() = this[length - 1]
set(value) {
this.setCharAt(length - 1, value)
}
val sb = StringBuilder("Kotlin")
println(sb.lastChar) // n
sb.lastChar = '!'
println(sb) // Kotli!
lastCharプロパティはKotlinドキュメントの古典的な例です。GetterはStringBuilderの最後の文字を返し、setterはそれを新しい値に置き換えます。注意:状態は個別のフィールドではなく、StringBuilder自体(setCharAt経由)に保存されます — これはextension propertyの正しい使用法です。
実際のプロジェクトでは、extension propertiesは主にコレクションデータへのアクセスを簡素化したり、UI要素のサイズやステータスを計算したり、既存のクラス上に便利なAPIを作成したりするために使用されます。Kotlin標準ライブラリはこのメカニズムを積極的に使用しています:コレクションのsize、indices、lastIndexはextension propertiesです。
// コレクション用extension properties
val List<Int>.sumFast: Int
get() = fold(0) { acc, i -> acc + i }
val String.half: String
get() = this.substring(0, length / 2)
// Android View用extension property
val View.isVisible: Boolean
get() = visibility == View.VISIBLE
// 安全なreceiverによるnullチェック
val String?.isNullOrBlank: Boolean
get() = this == null || this.isBlank()
Viewのextension property isVisibleは、すべてのAndroid開発者が知っておくべき例です。view.visibility == View.VISIBLEの代わりに、view.isVisibleと書くことができます。これは短いだけでなく、自然言語のように読めます:「ビューが表示されている場合」。シンプルさにもかかわらず、このようなプロパティはコードの可読性を大幅に向上させます。
よくある質問
いいえ、extension propertiesはcompanion objectやobject declarationに対して宣言できません。拡張メカニズムはクラス、インターフェース、null許容型にのみ適用されます。objectには、通常のトップレベル関数を使用してください。
Inlineプロパティ(inline修飾子付き)は、プロパティオブジェクトを作成せずにgetter/setterを呼び出すKotlinのメカニズムです。Extension propertyは常に静的メソッドにコンパイルされ、inlineプロパティはラッパーなしの呼び出しにコンパイルされます。これらは異なる問題を解決します:extension propertyは既存の型にプロパティを追加し、inlineは自身のプロパティの呼び出しを最適化します。
はい、extension propertyはアノテーションを持つことができますが、宣言レベルでのみです。通常のクラスプロパティとは異なり、extension propertyのgetterまたはsetterを個別にアノテーションすることはできません。例:@JvmName("getIsValid") val String.isValid get() = true.
いいえ、extension propertiesはcompanion objectをreceiverとして宣言できません。これは言語の制限です — extension propertyは型のインスタンスでのみ機能し、companion objectは静的コンテキストです。トップレベルのextension関数または定数を使用してください。
最小限です。各extension propertyは、コンパイルされたバイトコードに1つの静的getterメソッド(およびオプションでsetter)を追加します。比較すると、同じプロパティを持つラッパークラスを作成すると、クラス全体が追加されます。Extension propertiesは機能を拡張するためのより軽量なアプローチです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。