Edit Page

関数的インターフェース(SAM)

抽象メソッド一つだけを持つインターフェースを 関数的インターフェース(functional interface)、 または Single Abstract Method(SAM)インターフェース(訳注:単一抽象メソッド)と呼ばれます。 関数的インターフェースは複数の非抽象メンバを持つ事も出来ますが、抽象メンバは一つだけです。

Kotlinで関数的インターフェースを定義するには、fun修飾子を使います。

fun interface KRunnable {
   fun invoke()
}

SAMコンバージョン

関数的インターフェースに対しては、 SAMコンバージョンというものを使って、 ラムダ式を使ってコードを読みやすく簡潔に書く事が出来ます。

関数的インターフェースを実装したクラスを手作業で作る代わりに、 ラムダ式を使う事が出来ます。 SAMコンバージョンを用いると、Kotlinはインターフェースの単一のメソッドのシグニチャとマッチするラムダ式を、 動的にインターフェース実装をしたインスタンスとして生成するコードに変換出来ます。

例として、以下のようなKotlinの関数的インターフェースを考えましょう:

fun interface IntPredicate {
   fun accept(i: Int): Boolean
}

SAMコンバージョンを使わなければ、以下のようなコードを書かなくてはいけない所です:

// クラスのインスタンスを作成
val isEven = object : IntPredicate {
   override fun accept(i: Int): Boolean {
       return i % 2 == 0
   }
}

KotlinのSAMコンバージョンの力を使えば、同等の以下のようなコードを書く事が出来ます:

// ラムダを使ってインスタンスを作成
val isEven = IntPredicate { it % 2 == 0 }

短いラムダ式が不要なすべてのコードを置き換えてしまいました。

fun interface IntPredicate { fun accept(i: Int): Boolean } val isEven = IntPredicate { it % 2 == 0 } fun main() { println("7 は偶数? - ${isEven.accept(7)}") }

Javaのインターフェースに対するSAMコンバージョンというのもあります。

コンストラクタ関数付きインターフェース、から関数的インターフェースへのマイグレーション

(訳注:良く知らない機能なので原題を残しておく: Migration from an interface with constructor function to a functional interface)

1.6.20から、Kotlinは「呼び出し可能リファレンスから関数的インターフェースを作るコンストラクタ」、という機能をサポートしている。 (訳注:callable references to functional interface constructors という名前の機能らしい)

これはコンストラクタ関数付きのインターフェースから関数的インターフェースへのソース互換を保ったままのマイグレーション方法を提供する。 以下のコードを考えてみよう:

interface Printer { 
    fun print() 
}

fun Printer(block: () -> Unit): Printer = object : Printer { override fun print() = block() }

「呼び出し可能リファレンスから関数的インターフェースを作るコンストラクタ」がenableだと、 このコードは単なる関数的インターフェースの宣言に置き換える事が出来る:

fun interface Printer { 
    fun print()
}

コンストラクタは暗黙で作られて、::Printer関数のリファレンスを使うどんなコードでもコンパイル出来るようになります。 例えば:

documentsStorage.addPrinter(::Printer)

バイナリ互換を保つためには、レガシーとなるPrinter関数の方に @Deprecatedアノテーションを、 DeprecationLevel.HIDDENで付けます:

@Deprecated(message = "Your message about the deprecation", level = DeprecationLevel.HIDDEN)
fun Printer(...) {...}

関数的インターフェース vs. typeエイリアス

先程のコードを関数の型にtypeエイリアスを使って単純に以下のように書き直す事も出来ますが:

typealias IntPredicate = (i: Int) -> Boolean

val isEven: IntPredicate = { it % 2 == 0 }

fun main() {
   println("7は偶数? - ${isEven(7)}")
}

しかしながら、関数的インターフェースとtypeエイリアスは違う目的に使われるものです。 Typeエイリアスは既存の型に対する単なる名前に過ぎず、新しい型を作る訳でhありません。 一方、関数的インターフェースは新しい型を作ります。 特定の関数的インターフェースに拡張を提供する事が出来て、 それらの拡張は単なる関数やそのエイリアスには適用出来ない、という風に出来ます。

Typeエイリアスはメンバを一つしか持てませんが、 関数的インターフェースは非abstractのメンバを複数持つ事が出来ます(abstractのメンバが一つあれば)。 関数的インターフェースは他のインターフェースを実装したりextendしたり出来ます。

関数的インターフェースはtypeエイリアスと比べて、より柔軟でより多くの機能を提供します。しかし、実行コストもシンタックス的にもよりコストが掛かりもします。 なぜなら特定のインターフェースへの変換を必要とするからです。 あなたのコードでどちらを使ったらいいかを選ぶ時には、以下の必要性について考えてみましょう:

  • もしあなたのAPIがある特定のパラメータと戻りの型の関数ならなんでも受け付ける、というものなら、単純な関数の型かそのtype aliasを使って短い名前を与えたものを使いましょう。
  • もしあなたのAPIが単なる関数よりも複雑な何かを受け取るというものなら ー 例えば、関数の型のシグニチャで表現出来ないような、非自明のコントラクトやオペレーションを持っているなど ー その場合は別個の関数的インターフェースをそれ専用に定義しましょう。