Reflexão com Kotlin

Reflexão com Kotlin

1. Introdução

Reflexão é o nome da capacidade de inspecionar, carregar e interagir com classes, campos e métodos em tempo de execução. Podemos fazer isso mesmo quando não sabemos o que são em tempo de compilação.

Isso tem um grande número de usos, dependendo do que estamos desenvolvendo. Por exemplo, estruturas como o Spring fazem muito uso dela.

O suporte para isso é incorporado à JVM e, portanto, está implicitamente disponível para todos os idiomas baseados na JVM. No entanto, alguns idiomas da JVM têm suporte extra sobre o que já está disponível.

2. Reflexão Java

All the standard Java Reflection constructs are available and work perfectly well with our Kotlin code. Isso inclui a classejava.lang.Class, bem como tudo no pacotejava.lang.reflect.

Se quisermos usar as APIs Java Reflection padrão por qualquer motivo, podemos fazê-lo exatamente da mesma maneira que em Java. Por exemplo, para obter uma lista de todos os métodos públicos em uma classe Kotlin, faríamos:

MyClass::class.java.methods

Isso divide-se nas seguintes construções:

  • MyClass::class nos dá a representação da classe Kotlin para a classeMyClass

  • .java nos dá o equivalente ajava.lang.Class

  • .methods é uma chamada para o método acessadorjava.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. Isso inclui construções específicas do Kotlin, como Classes de dados.

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

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

O Kotlin também converte os tipos retornados nas representações do Kotlin.

Acima, obtemos umkotlin.Array<Method> no qual podemos chamarforEach().

3. Aprimoramentos de reflexão de Kotlin

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

Além disso, às vezes pode ser um pouco estranho usar em algumas situações. Kotlin traz sua própria API de reflexão que podemos usar para resolver esses problemas.

Todos os pontos de entrada na API Kotlin Reflection usam referências. Anteriormente, vimos o uso de::class para fornecer uma referência à definição de classe. Também poderemos usar isso para obter referências a métodos e propriedades.

3.1. Referências de classe 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. Isso dá acesso à referência da classe Java - o objetojava.lang.Class - mas também a todos os detalhes específicos do Kotlin.

A API Kotlin para detalhes da classe gira em torno da classekotlin.reflect.KClass. This can be accessed by using the :: operator from any class name or instance - por exemplo String::class.

Como alternativa, ele pode ser acessado usando o método de extensãojava.lang.Class.kotlin se uma instância JavaClass estiver disponível para nós:

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. Alguns deles são conceitos Java padrão e outros são conceitos específicos do Kotlin.

Por exemplo, podemos descobrir facilmente se uma classe é abstrata ou final, mas também podemos descobrir se a classe é uma classe de dados ou uma classe complementar:

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

Também temos maneiras de percorrer a hierarquia de classes. Em Java, já podemos passar de uma classe para sua superclasse, interfaces e a classe externa em que está incluída - se apropriado.

Kotlin adiciona a isso a capacidade de obter o objeto Companion para uma classe arbitrária e a instânciaObject para uma classe 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, da mesma forma que em Java:

val listClass = ArrayList::class

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

Como alternativa, podemos acessar os construtores e usar um explícito, se necessário. Essas são todas as referências de método, conforme discutido na próxima seção.

De uma maneira muito semelhante, podemos obter acesso a todos os métodos, propriedades, extensões e outros membros da classe:

val bigDecimalClass = BigDecimal::class

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

3.2. Referências do método Kotlin

Além de poder interagir com as Classes,we can also interact with Methods and Properties.

Isso inclui propriedades de classe - definidas comval ouvar, métodos de classe padrão e funções de nível superior. Como antes, isso funciona igualmente bem no código escrito em Java padrão, assim como no código escrito no Kotlin.

Exatamente da mesma forma que com as classes,we can obtain a reference to a Method or Property using the:: operator.

Parece exatamente o mesmo que no Java 8 para obter uma referência de método, e podemos usá-lo exatamente da mesma maneira. No entanto, no Kotlin, essa referência de método também pode ser usada para obter informações de reflexão sobre o alvo.

Once we have obtained a method reference, we can call it as if was really the method in question. Isso é conhecido como uma referência de chamada:

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

assertEquals(5, lengthMethod())

Também podemos obter mais detalhes sobre o método em si, da mesma maneira que podemos para as classes. Isso inclui detalhes Java padrão, bem como detalhes específicos do Kotlin, como se o método éoperator ouinline:

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.

Isso inclui detalhes sobre o tipo de retorno e os parâmetros, incluindo detalhes específicos do Kotlin - como anulabilidade e opcionalidade.

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. Referências de propriedade de Kotlin

This works exactly the same for Properties as well, embora obviamente os detalhes que podem ser obtidos sejam diferentes. As propriedades podem nos informar se são constantes, inicializadas tardiamente ou mutáveis:

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.

Isso inclui classes na biblioteca padrão Java. Por exemplo, a classeThrowable possui uma PropriedadeThrowable.message em virtude do fato de que existe um métodogetMessage() definido nela.

We can access the actual Property through Method references that are exposed - os métodosgetteresetter. Osetter só está disponível se estivermos trabalhando com umKMutableProperty –, ou seja, a propriedade foi declarada comovar, enquantogetter está sempre disponível.

Eles são expostos de uma forma mais fácil de usar pelos métodosget()eset(). Os valoresgetteresetter são referências de método reais, permitindo-nos trabalhar com eles exatamente da mesma forma que qualquer outra referência de método:

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. Sumário

Este artigo fornece uma visão geral de algumas das coisas que podem ser alcançadas com a reflexão no Kotlin, incluindo como ele interage e difere dos recursos de reflexão incorporados na linguagem Java padrão.

Todos os exemplos estão disponíveisover on GitHub.