Edit Page

リフレクション

リフレクションは実行時にあなたのプログラム自身の構造を調べる(introspect)ことを可能とする、言語機能とライブラリ機能をあわせたものです。 関数やプロパティはKotlinにおいてはファーストクラスの市民であり、 それらを調べる事が出来るのは(例えばプロパティや関数の名前や型を実行時に知る事など)、関数型のスタイルやリアクティブプログラムのスタイルを使う時には必須となります。

Kotlin/JS はリフレクションの機能については限定的にしかサポートしていません。 より詳しくはKotlin/JSにおけるリフレクションを参照のこと

JVMの時の依存ライブラリ

JVMのプラットフォームでは、Kotlinコンパイラの配布にはリフレクションを使うのに必要な機能の実行時コンポーネントが、 別のartifactとして含まれています。 その名もkotlin-reflect.jarです。 これは、リフレクションを使わないアプリの実行時のサイズを減らすためにこうなっています。

GradleやMaveのプロジェクトでリフレクションを使うには、kotlin-reflectへのdependencyを追加してください。

  • GradleでKotlinの場合:

      dependencies {
          implementation(kotlin("reflect"))
      }
    
  • GradleでGroovyの場合:

      dependencies {
          implementation "org.jetbrains.kotlin:kotlin-reflect:%kotlinVersion%"
      }
    
  • Mavenの場合:

      <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
        </dependency>
      </dependencies>
    

もしGradleもMavenも使ってないなら、プロジェクトのclasspathにkotlin-reflect.jarが含まれている事を確かめてください。 その他のサポートしているケース(IntelliJ IDEA プロジェクトでコマンドラインのコンパイラを使っていたりAntの場合)では、 kotlin-reflect.jarはデフォルトで追加されます。 コマンドラインのコンパイラやAntでkotlin-reflect.jarを除外したければ、 -no-reflectコンパイラオプションを使用出来ます。

classのリファレンス

リフレクションのもっとも基本的な機能としては、Kotlinクラスへの実行時リファレンスを取得する、というものが挙げられます。 静的に分かっているKotlinのクラスのリファレンスを取得するためには、classリテラルのシンタックスを使う事が出来ます:

val c = MyClass::class

リファレンスはKClass型の値となります。

JVMでは: Kotlinクラスのリファレンスは、Javaのクラスリファレンスと同じものではありません。Javaのクラスリファレンスを取得するには、 KClassのインスタンスの.javaプロパティを使用してください。

束縛されたクラスのリファレンス

(訳注:Bound class references)

あるオブジェクトに対して、そのオブジェクトのクラスのリファレンスを取得するのも、対象のオブジェクトをレシーバーに同様の::classシンタックスで取り出すことが出来ます:

val widget: Widget = ...
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }

オブジェクトの実際のクラスそのものを取得出来ます。例えばこの場合はGoodWidgetBadWidgetが取得出来ます。 たとえレシーバーの式の型がWidgetだとしてもです。

呼び出し可能リファレンス

(訳注:Callable references)

関数、プロパティ、コンストラクタのリファレンスは、 呼び出したり、関数の型のインスタンスとして使うことが出来ます。

すべての呼び出し可能なリファレンスの共通の基底クラスはKCallable<out R>です。 ここでRは戻りの型です。プロパティの場合はプロパティの型で、コンストラクタの場合はそれが作成するオブジェクトの型です。

関数リファレンス

名前の関数を以下のように宣言してあれば、直接それを呼ぶ事はもちろん出来ます (isOdd(5)):

fun isOdd(x: Int) = x % 2 != 0

それとは別に、関数を関数の型の値として使う事も出来ます。 つまり、別の関数に渡したり出来るという事です。 それをする為には::演算子を使います:

fun isOdd(x: Int) = x % 2 != 0 fun main() { //sampleStart val numbers = listOf(1, 2, 3) println(numbers.filter(::isOdd)) //sampleEnd }

ここでは、 ::isOdd は、関数の型 (Int) -> Boolean の値です。

関数のリファレンスはKFunction<out R>の派生クラスに属します。 どのクラスかはパラメータの個数に寄ります。 例えば、KFunction3<T1, T2, T3, R>などです。

期待する型が文脈から分かる場合は、::を多重定義された型に使うことも出来ます。 例えば:

fun main() { //sampleStart fun isOdd(x: Int) = x % 2 != 0 fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove" val numbers = listOf(1, 2, 3) println(numbers.filter(::isOdd)) // isOdd(x: Int)を参照 //sampleEnd }

または、明示的に型を指定した変数にメソッドリファレンス(訳注:関数リファレンスのことか)を格納することで、必要な文脈を与えることも出来ます:

val predicate: (String) -> Boolean = ::isOdd   //  isOdd(x: String)を参照

クラスのメンバ関数や拡張関数を使いたければ、限定子をつける(qualified)必要があります:String::toCharArrayのように。

拡張関数で変数を初期化しても、その変数の型はレシーバー無しの型で、レシーバーのオブジェクトを受け取る追加のパラメータがある関数として推論されてしまいます。 もしレシーバー付き関数の型としたければ、型を明示的に指定します:

val isEmptyStringList: List<String>.() -> Boolean = List<String>::isEmpty

例: 関数の合成

以下の関数を考えてみます:

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
    return { x -> f(g(x)) }
}

これは渡された2つの関数を合成した関数を返します:compose(f, g) = f(g(*))

この関数を呼び出し可能リファレンスに対して適用する(applyする)ことが出来ます:

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C { return { x -> f(g(x)) } } fun isOdd(x: Int) = x % 2 != 0 fun main() { //sampleStart fun length(s: String) = s.length val oddLength = compose(::isOdd, ::length) val strings = listOf("a", "ab", "abc") println(strings.filter(oddLength)) //sampleEnd }

プロパティリファレンス

プロパティをファーストクラスのオブジェクトとしてアクセスするには、Kotlinでは::オペレータを使います:

val x = 1

fun main() {
    println(::x.get())
    println(::x.name) 
}

::xという式はKProperty0<Int>型のプロパティオブジェクトとして評価されます。 その値はget()を用いて読み出すことが出来るし、プロパティの名前はnameプロパティを使って取り出すことが出来ます。 もっと詳細を知りたい人は、KPropertyクラスのドキュメントを参照してください。

var y = 1のようなミュータブルなプロパティの場合だと、::yKMutableProperty0<Int>型の値を返します。 これはset()メソッドを持ちます:

var y = 1 fun main() { ::y.set(2) println(y) }

プロパティのリファレンスは、ジェネリック引数一つの関数が期待される場所で使うことが出来ます:

fun main() { //sampleStart val strs = listOf("a", "bc", "def") println(strs.map(String::length)) //sampleEnd }

クラスのメンバのプロパティにアクセスする為には、以下のように限定子をつけて限定します:

fun main() { //sampleStart class A(val p: Int) val prop = A::p println(prop.get(A(1))) //sampleEnd }

拡張プロパティの場合は以下のようにします:

val String.lastChar: Char get() = this[length - 1] fun main() { println(String::lastChar.get("abc")) }

Javaのリフレクションとのインターオペラビリティ

JVMプラットフォーム上では、標準ライブラリにはJavaのリフレクションオブジェクトとの相互間のマッピングを提供するリフレクションクラスの拡張(extension)を含んでいます(kotlin.reflect.jvmパッケージを参照のこと)。 例えば、Kotlinのプロパティを提供するようなバッキングフィールドやJavaのメソッドを探したければ、以下のように書く事が出来ます:

import kotlin.reflect.jvm.*
 
class A(val p: Int)
 
fun main() {
    println(A::p.javaGetter) // "public final int A.getP()"とプリントされる
    println(A::p.javaField)  // "private final int A.p"とプリントされる
}

あるJavaクラスに対応するKotlinクラスを取得するには、.kotlin拡張プロパティを使う事が出来ます:

fun getKClass(o: Any): KClass<Any> = o.javaClass.kotlin

コンストラクタリファレンス

コンストラクタも、通常のメソッドやプロパティのように参照する事が出来ます。 コンストラクタと同じパラメータを取り対応するオブジェクトを返す事を期待するような関数の型のオブジェクトを期待する場所では全て、コンストラクタのリファレンスを使用する事が出来ます。 コンストラクタは::オペレータをクラスの名前につける事で参照する事が出来ます。 以下のような、引数無しで戻りの型としてFoo型を期待するような関数を期待する関数を考えてみましょう:

class Foo

fun function(factory: () -> Foo) {
    val x: Foo = factory()
}

Fooクラスの引数零のコンストラクタ、::Fooを使えば、以下のように呼ぶ事が出来ます:

function(::Foo)

コンストラクタの呼び出し可能リファレンスは、そのパラメータの数に応じたKFunction<out R>の派生クラスの型となります。

束縛された関数やプロパティのリファレンス

(Bound function and property references)

あるオブジェクトのインスタンスメソッドを参照する、という事が出来ます:

fun main() { //sampleStart val numberRegex = "\\d+".toRegex() println(numberRegex.matches("29")) val isNumber = numberRegex::matches println(isNumber("29")) //sampleEnd }

matchesメソッドを直接呼ぶ代わりに、この例ではそれへのリファレンスを用いています。 そのようなリファレンスは、そのレシーバーを束縛しています。 そのようなリファレンスは(上の例のように)直接呼ぶ事も出来ますし、 関数の型を期待する式ならどこでも用いる事が出来ます:

fun main() { //sampleStart val numberRegex = "\\d+".toRegex() val strings = listOf("abc", "124", "a70") println(strings.filter(numberRegex::matches)) //sampleEnd }

束縛されたリファレンスと束縛されてないリファレンスの型を比較してみましょう。 束縛されたリファレンスはそのレシーバーがそのリファレンスに「添付」されています。 だからレシーバーの型はパラメータには現れません:

val isNumber: (CharSequence) -> Boolean = numberRegex::matches

val matches: (Regex, CharSequence) -> Boolean = Regex::matches

プロパティのリファレンスも束縛出来ます:

fun main() { //sampleStart val prop = "abc"::length println(prop.get()) //sampleEnd }

レシーバーにthisを指定する必要はありません:this::foo::foo は同じ意味となります。

束縛されたコンストラクタのリファレンス

(Bound constructor references)

内部クラス(inner class)のコンストラクタの束縛された呼び出し可能リファレンスを、 その外部クラスのインスタンスを提供する事で取得する事が出来ます:

class Outer {
    inner class Inner
}

val o = Outer()
val boundInnerCtor = o::Inner