Отражение с Kotlin

Отражение с Котлиным

1. Вступление

Отражение - это имя для проверки, загрузки и взаимодействия с классами, полями и методами во время выполнения. Мы можем сделать это, даже если не знаем, что они собой представляют во время компиляции.

Это имеет большое количество применений, в зависимости от того, что мы разрабатываем. Например, фреймворки, такие как Spring, активно используют его.

Поддержка этого встроена в JVM и, таким образом, неявно доступна для всех языков, основанных на JVM. Однако некоторые языки JVM имеют дополнительную поддержку поверх того, что уже доступно.

2. Java Отражение

All the standard Java Reflection constructs are available and work perfectly well with our Kotlin code. Сюда входит классjava.lang.Class, а также все, что есть в пакетеjava.lang.reflect.

Если по какой-либо причине мы хотим использовать стандартные API-интерфейсы Java Reflection, мы можем сделать это точно так же, как в Java. Например, чтобы получить список всех открытых методов в классе Kotlin, мы должны сделать:

MyClass::class.java.methods

Это разбивается на следующие конструкции:

  • MyClass::class дает нам представление класса Котлина для классаMyClass

  • .java дает нам эквивалентjava.lang.Class

  • .methods - это вызов метода доступаjava.lang.Class.getMethods()

This will work exactly the same whether called from Java or Kotlin, and whether called on a Java or a Kotlin class. Это включает в себя специфические конструкции Kotlin, такие как классы данных.

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

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

Kotlin также преобразует возвращаемые типы в представления Kotlin.

Выше мы получилиkotlin.Array<Method>, на котором мы можем вызватьforEach().

3. Улучшения отражения в Котлине

Whilst we can use the standard Java Reflection APIs, it isn’t aware of all extensions that Kotlin brings to the platform.

Кроме того, иногда это может быть немного неудобно для использования в некоторых ситуациях. Kotlin предоставляет собственный API-интерфейс отражения, который мы можем использовать для решения этих проблем.

Все точки входа в Kotlin Reflection API используют ссылки. Ранее мы видели использование::class для ссылки на определение класса. Мы также сможем использовать это для получения ссылок на методы и свойства.

3.1. Ссылки на классы Kotlin

The Kotlin Reflection API allows access to a Class reference. This can then be used to introspect the full details of the Kotlin class. Это дает доступ к ссылке Java Class - объектуjava.lang.Class - но также и ко всем специфическим деталям Kotlin.

Kotlin API для подробностей класса сосредоточен вокруг классаkotlin.reflect.KClass. This can be accessed by using the :: operator from any class name or instance - например, String::class.

В качестве альтернативы, к нему можно получить доступ с помощью метода расширенияjava.lang.Class.kotlin, если нам доступен экземпляр JavaClass:

val listClass: KClass = List::class

val name = "example"
val stringClass: KClass = name::class

val someClass: Class
val kotlinClass: KClass = someClass.kotlin

Once we have obtained a KClass object, there are some simple things that it can tell us about the class in question. Некоторые из них являются стандартными концепциями 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 добавляет к этому возможность получить объект Companion для произвольного класса и экземплярObject для класса Object:

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

We can create new instances of a class from a Class Reference as well почти так же, как в 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. Ссылки на методы Котлина

Помимо возможности взаимодействовать с классамиwe can also interact with Methods and Properties.

Сюда входят свойства класса, определенные с помощьюval илиvar, стандартные методы класса и функции верхнего уровня. Как и прежде, это работает одинаково хорошо для кода, написанного на стандартном Java, так же, как и для кода, написанного на Kotlin.

Точно так же, как и с классами,we can obtain a reference to a Method or Property using the:: operator.

Это выглядит точно так же, как в Java 8, чтобы получить ссылку на метод, и мы можем использовать его точно так же. Однако в Kotlin эта ссылка на метод также может быть использована для получения информации об отражении цели.

Once we have obtained a method reference, we can call it as if was really the method in question. Это называется Callable

val str = "Hello"
val lengthMethod = str::length

assertEquals(5, lengthMethod())

Мы также можем получить более подробную информацию о самом методе, таким же образом, как и для классов. Это включает как стандартные детали Java, так и специфические детали Kotlin, например, является ли методoperator илиinline:

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

In addition to this, we can get more information about the inputs and outputs of the method through this reference.

Это включает в себя сведения о типе возвращаемого значения и параметрах, в том числе конкретные сведения о 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. Ссылки на недвижимость Котлина

This works exactly the same for Properties as well, хотя, очевидно, детали, которые можно получить, отличаются. Вместо этого свойства могут сообщить нам, являются ли они константами, поздними инициализациями или изменяемыми:

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

Note that the concept of Properties also works in any non-Kotlin code. These are identified by fields that follow the JavaBeans conventions regarding getter and setter methods.

Это включает в себя классы в стандартной библиотеке Java. Например, классThrowable имеет СвойствоThrowable.message в силу того, что в нем определен методgetMessage().

We can access the actual Property through Method references that are exposed - методыgetter иsetter. setter доступен, только если мы работаем сKMutableProperty –, т.е. свойство было объявлено как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.

Доступны все примерыover on GitHub.