layout | title |
---|---|
reference |
リフレクション |
リフレクション
は実行時にあなたのプログラム自身の構造を調べる(introspect)ことを可能とする、言語機能とライブラリ機能をあわせたものです。
関数やプロパティはKotlinにおいてはファーストクラスの市民であり、
それらを調べる事が出来るのは(例えばプロパティや関数の名前や型を実行時に知る事など)、関数型のスタイルやリアクティブプログラムのスタイルを使う時には必須となります。
Kotlin/JS はリフレクションの機能については限定的にしかサポートしていません。 より詳しくはKotlin/JSにおけるリフレクションを参照のこと
{: .note}
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
コンパイラオプションを使用出来ます。
リフレクションのもっとも基本的な機能としては、Kotlinクラスへの実行時リファレンスを取得する、というものが挙げられます。
静的に分かっているKotlinのクラスのリファレンスを取得するためには、classリテラル
のシンタックスを使う事が出来ます:
val c = MyClass::class
リファレンスはKClass型の値となります。
JVMでは: Kotlinクラスのリファレンスは、Javaのクラスリファレンスと同じものではありません。Javaのクラスリファレンスを取得するには、
KClass
のインスタンスの.java
プロパティを使用してください。
{: .note}
(訳注:Bound class references)
あるオブジェクトに対して、そのオブジェクトのクラスのリファレンスを取得するのも、対象のオブジェクトをレシーバーに同様の::class
シンタックスで取り出すことが出来ます:
val widget: Widget = ...
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
オブジェクトの実際のクラスそのものを取得出来ます。例えばこの場合はGoodWidget
や BadWidget
が取得出来ます。
たとえレシーバーの式の型がWidget
だとしてもです。
(訳注:Callable references)
関数、プロパティ、コンストラクタのリファレンスは、 呼び出したり、関数の型のインスタンスとして使うことが出来ます。
すべての呼び出し可能なリファレンスの共通の基底クラスはKCallable<out R>
です。
ここでR
は戻りの型です。プロパティの場合はプロパティの型で、コンストラクタの場合はそれが作成するオブジェクトの型です。
名前の関数を以下のように宣言してあれば、直接それを呼ぶ事はもちろん出来ます (isOdd(5)
):
fun isOdd(x: Int) = x % 2 != 0
それとは別に、関数を関数の型の値として使う事も出来ます。
つまり、別の関数に渡したり出来るという事です。
それをする為には::
演算子を使います:
{% capture func-reference %} fun isOdd(x: Int) = x % 2 != 0
fun main() { //sampleStart val numbers = listOf(1, 2, 3) println(numbers.filter(::isOdd)) //sampleEnd } {% endcapture %} {% include kotlin_quote.html body=func-reference %}
ここでは、 ::isOdd
は、関数の型 (Int) -> Boolean
の値です。
関数のリファレンスはKFunction<out R>
の派生クラスに属します。
どのクラスかはパラメータの個数に寄ります。
例えば、KFunction3<T1, T2, T3, R>
などです。
期待する型が文脈から分かる場合は、::
を多重定義された型に使うことも出来ます。
例えば:
{% capture overload-function-reference %} 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 } {% endcapture %} {% include kotlin_quote.html body=overload-function-reference %}
または、明示的に型を指定した変数にメソッドリファレンス(訳注:関数リファレンスのことか)を格納することで、必要な文脈を与えることも出来ます:
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する)ことが出来ます:
{% capture compose-ex %} 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 } {% endcapture %} {% include kotlin_quote.html body=compose-ex %}
プロパティをファーストクラスのオブジェクトとしてアクセスするには、Kotlinでは::
オペレータを使います:
val x = 1
fun main() {
println(::x.get())
println(::x.name)
}
::x
という式はKProperty0<Int>
型のプロパティオブジェクトとして評価されます。
その値はget()
を用いて読み出すことが出来るし、プロパティの名前はname
プロパティを使って取り出すことが出来ます。
もっと詳細を知りたい人は、KProperty
クラスのドキュメントを参照してください。
var y = 1
のようなミュータブルなプロパティの場合だと、::y
は KMutableProperty0<Int>
型の値を返します。
これはset()
メソッドを持ちます:
{% capture mutable-prop-ref %} var y = 1
fun main() { ::y.set(2) println(y) } {% endcapture %} {% include kotlin_quote.html body=mutable-prop-ref %}
プロパティのリファレンスは、ジェネリック引数一つの関数が期待される場所で使うことが出来ます:
{% capture prop-ref-as-func %} fun main() { //sampleStart val strs = listOf("a", "bc", "def") println(strs.map(String::length)) //sampleEnd } {% endcapture %} {% include kotlin_quote.html body=prop-ref-as-func %}
クラスのメンバのプロパティにアクセスする為には、以下のように限定子をつけて限定します:
{% capture prop-ref-from-class %} fun main() { //sampleStart class A(val p: Int) val prop = A::p println(prop.get(A(1))) //sampleEnd } {% endcapture %} {% include kotlin_quote.html body=prop-ref-from-class %}
拡張プロパティの場合は以下のようにします:
{% capture prop-ref-of-extension %} val String.lastChar: Char get() = this[length - 1]
fun main() { println(String::lastChar.get("abc")) } {% endcapture %} {% include kotlin_quote.html body=prop-ref-of-extension %}
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)
あるオブジェクトのインスタンスメソッドを参照する、という事が出来ます:
{% capture instance-method-ref %} fun main() { //sampleStart val numberRegex = "\d+".toRegex() println(numberRegex.matches("29"))
val isNumber = numberRegex::matches
println(isNumber("29"))
//sampleEnd } {% endcapture %} {% include kotlin_quote.html body=instance-method-ref %}
matches
メソッドを直接呼ぶ代わりに、この例ではそれへのリファレンスを用いています。
そのようなリファレンスは、そのレシーバーを束縛しています。
そのようなリファレンスは(上の例のように)直接呼ぶ事も出来ますし、
関数の型を期待する式ならどこでも用いる事が出来ます:
{% capture bound-reference-to-filter %} fun main() { //sampleStart val numberRegex = "\d+".toRegex() val strings = listOf("abc", "124", "a70") println(strings.filter(numberRegex::matches)) //sampleEnd } {% endcapture %} {% include kotlin_quote.html body=bound-reference-to-filter %}
束縛されたリファレンスと束縛されてないリファレンスの型を比較してみましょう。 束縛されたリファレンスはそのレシーバーがそのリファレンスに「添付」されています。 だからレシーバーの型はパラメータには現れません:
val isNumber: (CharSequence) -> Boolean = numberRegex::matches
val matches: (Regex, CharSequence) -> Boolean = Regex::matches
プロパティのリファレンスも束縛出来ます:
{% capture bound-property %} fun main() { //sampleStart val prop = "abc"::length println(prop.get()) //sampleEnd } {% endcapture %} {% include kotlin_quote.html body=bound-property %}
レシーバーにthis
を指定する必要はありません:this::foo
と ::foo
は同じ意味となります。
(Bound constructor references)
内部クラス(inner class)のコンストラクタの束縛された呼び出し可能リファレンスを、 その外部クラスのインスタンスを提供する事で取得する事が出来ます:
class Outer {
inner class Inner
}
val o = Outer()
val boundInnerCtor = o::Inner