1. 序章

リフレクションは、実行時にクラス、フィールド、およびメソッドを検査、ロード、および操作する機能の名前です。 コンパイル時に何であるかわからない場合でも、これを行うことができます。

これは、私たちが開発しているものに応じて、多くの用途があります。 たとえば、Springのようなフレームワークはそれを多用します。

これのサポートはJVMに組み込まれているため、すべてのJVMベースの言語で暗黙的に使用できます。 ただし、一部のJVM言語では、すでに利用可能なものに加えて、追加のサポートがあります。

2. Javaリフレクション

すべての標準JavaReflection構造が利用可能であり、Kotlinコードで完全に機能します。 これには、 java.lang.Class クラスと、java.lang.reflectパッケージのすべてが含まれます。

何らかの理由で標準のJavaReflectionAPIを使用する場合は、Javaの場合とまったく同じ方法で使用できます。 たとえば、Kotlinクラスのすべてのパブリックメソッドのリストを取得するには、次のようにします。

MyClass::class.java.methods

これは、次の構成に分解されます。

  • MyClass :: class は、MyClassクラスのKotlinクラス表現を提供します
  • .java は、java.lang.Classと同等のものを提供します
  • .methods は、 java.lang.Class.getMethods()アクセサーメソッドの呼び出しです。

これは、JavaまたはKotlinから呼び出されても、JavaまたはKotlinクラスから呼び出されてもまったく同じように機能します。 これには、データクラスなどのKotlin固有の構造が含まれます。

data class ExampleDataClass(
  val name: String, var enabled: Boolean)

ExampleDataClass::class.java.methods.forEach(::println)

Kotlinは、返された型をKotlin表現にも変換します。

上記では、 kotlin.Array 呼び出すことができます forEach()。

3. Kotlinリフレクションの機能強化

標準のJavaReflectionAPIを使用できますが、Kotlinがプラットフォームにもたらすすべての拡張機能を認識しているわけではありません

さらに、状況によっては使用するのが少し厄介な場合があります。 Kotlinは、これらの問題を解決するために使用できる独自のリフレクションAPIを提供します。

Kotlin Reflection APIへのすべてのエントリポイントは、リファレンスを使用します。 以前、 ::classを使用してクラス定義への参照を提供するのを見ました。 これを使用して、メソッドとプロパティへの参照を取得することもできます。

3.1. Kotlinクラスリファレンス

Kotlin Reflection APIを使用すると、クラス参照にアクセスできます。 これを使用して、Kotlinクラスの詳細をすべて確認できます。 。 これにより、Javaクラス参照( java.lang.Class オブジェクト)へのアクセスが可能になりますが、Kotlin固有のすべての詳細へのアクセスも可能になります。

クラス詳細用のKotlinAPIは、kotlin.reflect.KClassクラスを中心にしています。 これには、任意のクラス名またはインスタンスから::演算子を使用してアクセスできます –例: String::class。

または、Java Class インスタンスが使用可能な場合は、拡張メソッドjava.lang.Class.kotlinを使用してアクセスできます。

val listClass: KClass<List> = List::class

val name = "Baeldung"
val stringClass: KClass<String> = name::class

val someClass: Class<MyType>
val kotlinClass: KClass<MyType> = someClass.kotlin

KClassオブジェクトを取得すると、問題のクラスについて教えてくれる簡単なことがいくつかあります。 これらのいくつかは標準のJavaの概念であり、その他はKotlin固有の概念です。

たとえば、クラスが抽象クラスであるか最終クラスであるかを簡単に確認できますが、クラスがデータクラスであるかコンパニオンクラスであるかを確認することもできます。

val stringClass = String::class
assertEquals("kotlin.String", stringClass.qualifiedName)
assertFalse(stringClass.isData)
assertFalse(stringClass.isCompanion)
assertFalse(stringClass.isAbstract)
assertTrue(stringClass.isFinal)
assertFalse(stringClass.isSealed)

クラス階層を移動する方法もあります。 Javaでは、必要に応じて、クラスからそのスーパークラス、インターフェイス、およびそれが含まれる外部クラスにすでに移動できます。

Kotlinは、これに任意のクラスのコンパニオンオブジェクト、およびオブジェクトクラスのObjectインスタンスを取得する機能を追加します。

println(TestWithCompanion::class.companionObject)
println(TestWithCompanion::class.companionObjectInstance)
println(TestObject::class.objectInstance)

Javaの場合とほぼ同じ方法で、クラス参照からクラスの新しいインスタンスを作成することもできます

val listClass = ArrayList::class

val list = listClass.createInstance()
assertTrue(list is ArrayList)

または、必要に応じて、コンストラクターにアクセスして明示的なコンストラクターを使用することもできます。 これらはすべて、次のセクションで説明するメソッドリファレンスです。

非常によく似た方法で、すべてのメソッド、プロパティ、拡張機能、およびクラスの他のメンバーにアクセスできます。

val bigDecimalClass = BigDecimal::class

println(bigDecimalClass.constructors)
println(bigDecimalClass.functions)
println(bigDecimalClass.memberProperties)
println(bigDecimalClass.memberExtensionFunctions)

3.2. Kotlinメソッドリファレンス

クラスと対話できることに加えて、メソッドとプロパティと対話することもできます。

これには、valまたはvarで定義されたクラスプロパティ、標準のクラスメソッド、および最上位関数が含まれます。 以前と同様に、これは標準のJavaで記述されたコードでも、Kotlinで記述されたコードでも同様に機能します。

クラスの場合とまったく同じ方法で、 ::演算子を使用してメソッドまたはプロパティへの参照を取得できます。

これは、メソッド参照を取得するためのJava 8とまったく同じように見え、まったく同じように使用できます。 ただし、Kotlinでは、このメソッド参照を使用して、ターゲットに関する反射情報を取得することもできます。

メソッド参照を取得したら、それを実際に問題のメソッドであるかのように呼び出すことができます。 これは、呼び出し可能な参照として知られています。

val str = "Hello"
val lengthMethod = str::length
        
assertEquals(5, lengthMethod())

クラスの場合と同じように、メソッド自体の詳細も取得できます。 これには、標準のJavaの詳細と、メソッドが演算子であるか、インライン:であるかなどのKotlin固有の詳細の両方が含まれます。

val byteInputStream = String::byteInputStream
assertEquals("byteInputStream", byteInputStream.name)
assertFalse(byteInputStream.isSuspend)
assertFalse(byteInputStream.isExternal)
assertTrue(byteInputStream.isInline)
assertFalse(byteInputStream.isOperator)

これに加えて、このリファレンスを介してメソッドの入力と出力に関する詳細情報を取得できます。

これには、null可能性やオプション性などのKotlin固有の詳細を含む、リターンタイプとパラメーターに関する詳細が含まれます。

val str = "Hello"
val method = str::byteInputStream

assertEquals(
  ByteArrayInputStream::class.starProjectedType, 
  method.returnType)
assertFalse(method.returnType.isMarkedNullable)

assertEquals(1, method.parameters.size)
assertTrue(method.parameters[0].isOptional)
assertFalse(method.parameters[0].isVararg)
assertEquals(
  Charset::class.starProjectedType, 
  method.parameters[0].type)

3.3. Kotlinプロパティリファレンス

これはプロパティでもまったく同じように機能しますが、明らかに取得できる詳細は異なります。 代わりに、プロパティは、それらが定数であるか、初期化が遅れているか、または可変であるかを通知できます。

lateinit var mutableProperty: String
val mProperty = this::mutableProperty
assertEquals("mutableProperty", mProperty.name)
assertTrue(mProperty.isLateinit)
assertFalse(mProperty.isConst)
assertTrue(mProperty is KMutableProperty<*>)

プロパティの概念は、Kotlin以外のコードでも機能することに注意してください。 これらは、getterメソッドとsetterメソッドに関するJavaBeansの規則に従うフィールドによって識別されます。

これには、Java標準ライブラリのクラスが含まれます。 たとえば、 Throwable クラスには、メソッド getMessage()が定義されているため、プロパティThrowable.messageがあります。

公開されているメソッド参照getterおよびsetterメソッドを介して実際のプロパティにアクセスできます。 セッターは、 KMutableProperty –ieを使用している場合にのみ使用できます。 プロパティはvarとして宣言されましたが、getterは常に使用可能です。

これらは、 get()および set()メソッドを介してより使いやすい方法で公開されます。 getterおよびsetterの値は実際のメソッド参照であり、他のメソッド参照とまったく同じように操作できます。

val prop = this::mutableProperty

assertEquals(
  String::class.starProjectedType, 
  prop.getter.returnType)

prop.set("Hello")
assertEquals("Hello", prop.get())

prop.setter("World")
assertEquals("World", prop.getter())

4. 概要

この記事では、Kotlinでリフレクションを使用して実現できるいくつかのことの概要を説明します。これには、標準のJava言語に組み込まれているリフレクション機能との相互作用と異なる方法の両方が含まれます。

すべての例は、GitHubから入手できます。