Concept of reflection

  • Reflection is a class of features that allow programs to access program structures at run time
  • Program structure includes: class, interface, method, attribute and other syntax features

Common uses of reflection

  • Lists all properties, methods, inner classes, and so on for the type
  • Invokes a method with the given name and signature or accesses a property with the specified name
  • Get the specific type of the generic argument from the signature information
  • Access runtime annotations and their information for injection or configuration

Reflect common data structures

The data structure Concept and instructions
KType Generic types or parameters described as erased, such as Map<String,String>, can be obtained from typeOf() or the following types
KClass Describes the actual type of the object, without generic parameters such as Map, which can be obtained by the object type name
KProperty Describes attributes, which can be obtained through a property reference, the KClass of the property’s class
KFunction Describes a function, which can be obtained from a function reference, the KClass of the function’s class

Kotlin uses reflection

The basic use

Introduction of depend on

implementation "org.jetbrains.kotlin:kotlin-reflect"

Copy the code
/ / get KClass
var cls: KClass<String> = String::class
/ / get KProperty
val property = cls.declaredMemberProperties.firstOrNull()
/ / get KMap
val mapType = typeOf<Map<String, Int>>()
 mapType.arguments.forEach {
        println(it)
    }

Copy the code

In general :: refers to a reference, followed by a function name to refer to a function, followed by an attribute to refer to a property

Fun add(){} class A {val age: Int = 10} val x: Int = 1 fun main() {Copy the code

Gets the generic argument

Target: Gets the generic parameters to erase

interface Api {
    fun getUsers(a): List<UserDTO>
}

abstract class SuperType<T> {}class SubType : SuperType<String>(a)Copy the code

So let’s get this UserDTO argument

// Get the getUsers method indirectly
Api::class.declaredFunctions.first { it.name == "getUsers" }
            .returnType.arguments.forEach {
                println(it)
            }
    
// Get the getUsers method directly
Api::getUsers.returnType.arguments.forEach {
        println(it)
    }
    
    // The Java method
Api::class.java.getDeclaredMethod("getUsers") .genericReturnType.safeAs<ParameterizedType>()? .actualTypeArguments? .forEach { println(it) }// Declare extension methods for easy use
 fun <T> Any.safeAs(a): T? {
    return this as? T
}
Copy the code

Get the SuperType generic

// The kotlin method
abstract class SuperType<T> {
    val typeParameter by lazy {
        this: :class.supertypes.first().arguments.first().type
    }
// The Java method
    val typeParameterJava by lazy {
        this.javaClass.genericSuperclass.safeAs<ParameterizedType>()!! .actualTypeArguments.first() } }Copy the code

Data classes implement DeepCopy

Let’s start by defining two data classes

data class Person(val id: Int.val name: String, val group: Group)

data class Group(val id: Int.val name: String, val location: String)
Copy the code

This Person class has an argument called Group

We create two objects

 val person = Person(
        0."Greathfs",
        Group(
            0."Kotlin"."China"))val copiedPerson = person.copy()
  //true
  println(person.group === copiedPerson.group)
Copy the code

If we call copy to get a Person object, then the Group in the Person object is the same as the Group in the Person object we created. If we change one, the other will be affected. Sometimes we don’t want this, so we implement a deepCopy method for it method

Main ideas:

1. All data classes have private constructors;

2. Get the parameters from the constructor

3. Get the values of the constructor parameters

4. Determine if the value is a data class, and if so, recursively call it

5. Call the constructor’s callBy method

Fun <T: Any> t.diepCopy (): T {// If (! this::class.isData){ return this } return this::class.primaryConstructor!! . Let {primaryConstructor - > / / constructor parameters, parameter and attribute primaryConstructor. The parameters. The map {parameter - > / / get the corresponding parameters val value = (this::class as KClass<T>).memberProperties. First {it. Name == parameter.name} if((parameter.type.classifier as? KClass<*>)?.isData == true){ parameter to value?.deepCopy() } else { parameter to value } }.toMap() .let(primaryConstructor::callBy) } }Copy the code

The Model mapping

The general idea is to get the property parameters through the constructor, iterate over the parameters, convert them to a map, and then call callBy

inline fun <reified From : Any, reified To : Any> From.mapAs(a): To {
    return From::class.memberProperties.map { it.name to it.get(this) }
        .toMap().mapAs()
}

inline fun <reified To : Any> Map
       ,>.mapAs(a): To {
    return To::class.primaryConstructor!! .let { it.parameters.map { parameter -> parameter to (this[parameter.name] ?: if(parameter.type.isMarkedNullable) null
            else throw IllegalArgumentException("${parameter.name} is required but missing."))
        }.toMap()
            .let(it::callBy)
    }
}
Copy the code