拡張関数(extension functions)は、KotlinがJavaと異なる主要な特徴の一つであり、C#からのインスピレーションを受けて言語の初期バージョンから登場しました。これは、既存のクラスに新しいメソッドを追加することを可能にし、継承や元のクラスの修正を必要としません。これにより、コードの表現力と簡潔さが大幅に向上します。
Javaや多くの他の言語では、既存のクラス(たとえば、StringやList)にメソッドを追加することは、継承やデコレーターパターンを使わない限り不可能です。Kotlinはこの欠点を拡張関数のメカニズムを通じて解決します。
多くのライブラリのクラス(例:String、Int、List)を直接変更することはできません。しばしば、コードをより読みやすくし、ロジックの重複を避けるために追加のメソッドが必要になります。しかし、拡張関数は実際のクラスを変更するわけではなく、静的に作成され、private/protectedメンバーにはアクセスできません。
Kotlinでの拡張関数の宣言は次のようになります:
fun String.lastChar(): Char = this[this.length - 1]
これで、任意のStringに対してこのメソッドを呼び出すことができます:
"Kotlin".lastChar() // 'n'を返す
拡張関数はクラスの子クラスでオーバーライドできますか?
いいえ。拡張関数はクラスのメンバーではなく、オーバーライドすることはできません。静的であるため、コンパイラは実際のオブジェクトのタイプではなく、式のタイプに基づいて関数を選択します。
open class Base class Derived : Base() fun Base.foo() = "base" fun Derived.foo() = "derived" fun printFoo(b: Base) { println(b.foo()) } val d = Derived() printFoo(d) // "base"を出力
拡張関数はオブジェクトの状態を変更できますか?
拡張関数はprivate/protectedの状態にアクセスできませんが、mutableな型(たとえば、Listや独自のクラス)であれば、公開されたメソッドを通じて外部の状態を変更できます。
fun MutableList<Int>.addTwice(elem: Int) { add(elem) add(elem) }
拡張関数は同じ名前のクラスのメンバーと衝突しますか?
拡張関数の名前が実際のメソッドと同じであれば、優先順位はクラスのメンバーに残ります。クラスのメンバーが存在しないか視認できない場合にのみ拡張関数が呼び出されます。
class Foo { fun bar() = "member" } fun Foo.bar() = "extension" val f = Foo() println(f.bar()) // "member"
開発者が標準Stringをプロジェクト固有の形式を設定する関数で拡張し、それを至る所で使用する — 他の開発者はこれがStringクラスの標準機能ではないことに気づかない。
長所:
短所:
標準クラスをユーティリティメソッドで拡張し、それがutilsパッケージに明確にローカライズされ、十分に文書化されている。使用は明示的に規定されたコード部分に制限されている。
長所:
短所: