BlogBracken

FOR_MYSELF

【和訳】Kotlinドキュメント - Reflection

訳あってKotlin公式ドキュメントのReflectionをオレオレ和訳しました。

粗が目立つとは思いますが、尚血迷った翻訳を見かけたら耳打ちしていただけると嬉しいです。

kotlinlang.org

リフレクション

まずリフレクションとは、ランタイム時にプログラムの構造をイントロスペクション*1させる 機能を持つライブラリと言語の集まりのことです。

Kotlinでは関数とプロパティを言語レベルで第一級オブジェクトとしています。 それによって関数やプロパティをイントロスペクションするということは、 Kotlinでシンプルな関数型プログラミングやリアクティブプログラミングを行うということと、とても密接に関係しています。

クラス参照

もっとも基礎的なリフレクションの機能として、Kotlinのクラスの参照を実行時に取得する、というものがあります。 静的なKotlinのクラスへの参照を取得するには、クラスリテラル構文を使います:

val c = MyClass::class

この参照の値はKClass型です。

Kotlinのクラスの参照は、Javaのクラスの参照とは同じものではないということに留意してください。 Javaのクラスの参照を取得するには、KClassインスタンス.javaというプロパティを使ってください。

束縛されたクラス参照(1.1以降)

特定のオブジェクトのクラスへの参照を、::class構文をオブジェクトに対して用いるように、レシーバへ用いることで取得することもできます。

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

レシーバは(Widget)型であるにも関わらず、GoodWidgetBadWidgetのような正確なオブジェクトのクラスの参照が得られます。

呼び出せる参照

プログラムの構造をイントロスペクションすることから離れた、関数やプロパティ、コンストラクタへの参照は、 関数型インスタンスとして呼び出し、利用することが出来ます。

全ての呼び出せる参照型に共通したスーパータイプはKCallable<out R>です。 このR型は返り値の型で、プロパティならプロパティの型、コンストラクタなら生成された型になります。

関数参照

例えば

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

という関数を作ったとしましょう。

私達はisOdd(5)と書けば容易く直接この関数を呼び出せます。

これに限らず、この関数は関数型の値として使うこともできます(例えば、他の関数の引数に与えたい時に使います)。

関数型の値にする為には ::演算子を用います:

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))

ここでの::isOdd(Int) -> Booleanという関数型の値です。

関数型の参照はKFunction<out R>のサブタイプの一つになります。 このサブタイプは引数の数に依存します(例えばKFunction3<T1, T2, T3, R>)。

::演算子オーバーロードされた関数であっても, 型が推論出来る文脈であれば用いることが出来ます. 例えば:

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)への参照

また、必要に応じて明示された型の変数へ代入することもできます。

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

もし(他の)クラスのメンバ変数や拡張関数への参照を使うときには、String::toCharArrayのように(::の左辺に)必要な型を補う必要があります。

拡張関数への参照を変数に代入したとしても、 そこから推論された変数の型はレシーバを持たないことに留意してください(レシーバオブジェクトを取る引数を受け取ります)。

もしレシーバを持たせたければ、型を明示する必要があります。

val isEmptyStringList: List<String>.() -> Boolean = List<String>::isEmpty
// 訳注) 型を明示しなければ (List<String>) -> Boolean 型になります。

例: 関数合成

下のような関数について考えてみましょう:

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

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

訳注) compose(f, g)(typeA_value) = f(g(typeA_value))ということです。f∘gと同じ。

すると、この合成した関数には関数参照を適用することが出来ます:

fun length(s: String) = s.length

val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")

println(strings.filter(oddLength))

プロパティ参照

Kotlinでの第一級オブジェクトであるプロパティにアクセスするには、またもや::演算子を使います:

val x = 1

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

::xという式は、KProperty<Int>型のオブジェクトとして評価されます。 KProperty<Int>型では、get()を使うことで値を得て、nameプロパティによってプロパティ名を取得することが出来るようになります。

より詳細な情報が必要であれば、KPropertyクラスのドキュメントを参照してください。

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

var y = 1

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

プロパティの参照は一つだけ引数を取る関数として利用することもできます:

val strs = listOf("a", "bc", "def")
println(strs.map(String::length))

適切な引数を与えることで、クラスのメンバであるプロパティにもアクセスできます:

class A(val p: Int)
val prop = A::p
println(prop.get(A(1)))

拡張プロパティの場合:

val String.lastChar: Char
    get() = this[length - 1]

fun main() {
    println(String::lastChar.get("abc"))
}

Javaのリフレクションとの相互運用性

Javaプラットフォーム上では、標準ライブラリがJavaのリフレクションオブジェクトとの相互運用を提供するための拡張を含んでいます(kotlin.reflect.jvmを参照してください)。

例えば、JavaのメソッドやバッキングフィールドはKotlinプロパティのgetterによって提供されており、このように利用することが出来ます:

import kotlin.reflect.jvm.*
 
class A(val p: Int)
 
fun main() {
    println(A::p.javaGetter) // prints "public final int A.getP()"
    println(A::p.javaField)  // prints "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>のサブタイプの一つです。 これも引数の数に依存します。

束縛された関数とプロパティの参照(1.1以降)

特定のオブジェクトのメソッドを参照することが出来ます:

val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29"))

val isNumber = numberRegex::matches
println(isNumber("29"))

matchesというメソッドを直接呼び出す代わりに、参照を保管することが出来ます。 そのような参照はレシーバによって束縛*2されます。 それは(下の例のように)直接呼び出すか、推論された関数型の式として使われます:

val numberRegex = "\\d+".toRegex()
val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches))

束縛がない参照と、束縛された参照の関係を比較してみましょう。 束縛された呼び出せる参照は自身のレシーバーが束縛されているため、レシーバの型はパラメータにはなり得ません。

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

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

プロパティの参照にも同様に、束縛を付けることが出来ます:

val prop = "abc"::length
println(prop.get())

Kotlin 1.2以降では、束縛されたレシーバをthisで明示する必要がなくなりました。 現行ではthis::foo::fooは同じものです。

束縛されたコンストラクタの参照

束縛された呼び出し可能なインナークラスのコンストラクタの参照によって、アウタークラスのインスタンスを取得することが出来ます:

class Outer {
    inner class Inner
}

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

*1:オブジェクトの型やプロパティを実行時に調べる機能のこと

*2:このページでの束縛というのは、全体的にレシーバの束縛の事