Using Kotlin reflection skillfully to name by value, debugging more quickly and save money

Posted by Sh0t on Mon, 01 Jun 2020 03:04:06 +0200

Pain spot

We often define constants, such as

1
2
3
4
5
6
7
public interface ItemType {
    public static final int TYPE_TEXT = 0;
    public static final int TYPE_IMG = 1;
    public static final int TYPE_VIDEO = 2;
    public static final int TYPE_AUDIO = 3;
    public static final int TYPE_LINK = 4;
}

When we print to see what type it is, if we just print int value, it is not enough to explain business information, so in order to better output information, we usually do this

1
2
3
4
5
6
7
8
9
10
11
private fun inspectItemTypeUgly(itemType: Int) {
    val type = when(itemType) {
        ItemType.TYPE_TEXT -> "text"
        ItemType.TYPE_AUDIO -> "audio"
        ItemType.TYPE_IMG -> "image"
        ItemType.TYPE_LINK -> "link"
        ItemType.TYPE_VIDEO -> "video"
        else -> null
    }
    println("inspect item type =${inspectItemTypeUgly(itemType)};originalValue=$itemType")
}

Then call it like this

1
2
val itemType = getRandomItemType()
print(inspectItemTypeUgly(itemType))

This can print out more meaningful information, but it is time-consuming and laborious to write additional methods to convert int into String.

Is there a good way

There is a way.

For example, we define public static final int type_ When text = 0;, we define the constant name and constant value. that

  • We can use the variable value to find the corresponding variable name
  • With the convenient features and reflection Library of Kotlin, we can realize it better and more easily.

Show code

For Java classes (interfaces) and ordinary Kotlin classes

1
2
3
4
5
6
7
8
fun <T> getConstantNameByValueFromNormalClass(kClass: KClass<*>, value: T): String? {
    value ?: return null
    return kClass.staticProperties.filter {
        it.isFinal
    }.firstOrNull() {
        it.getter.call() == value
    }?.name
}

Call example

1
println("itemType=${ItemType::class.getConstantNameByValueFromNormalClass(itemType)}")

For Kotlin object

1
2
3
4
5
6
7
8
fun <T> getConstantNameByValueForObject(kClass: KClass<*>, value: T): String? {
    value ?: return null
    return kClass.memberProperties.filter {
        it.isFinal
    }.firstOrNull {
        it.getter.call() == value
    }?.name
}

Call example

1
2
3
4
5
6
7
8
//Defining constants in Object objects
object ErrorCodes {
    const val ERROR_OK = 0
    const val ERROR_INVALID_PARAM = 1
}

//Call
println("errorCode=" + ErrorCodes::class.getConstantNameByValueForObject(0))

The problem of Kotlin Top Level variable

  • For Kotlin variable definition, we recommend to define it at the top level level.
  • But Kotlin cannot directly access the top level class
  • We need to use some variables to help get the top level level class
  • But Kotlin's reflection cannot be top level, so we must use java class

With a variable or top class

1
2
3
4
const val SOURCE_REMOTE = 0
const val SOURCE_LOCAL = 1

val myConstantTopClass = object : Any() {}.javaClass.enclosingClass

Get constant with Java class (exception not supported will be thrown with KClass)

1
2
3
4
5
6
7
8
9
10
fun <T> Class<*>.getConstantNameByValues(value: T): String? {
    value ?: return null
    return declaredFields.mapNotNull {
        it.kotlinProperty
    }.filter {
        it.isFinal
    }.firstOrNull {
        it.getter.call() == value
    }?.name
}

Call

1
println("sourceType=" + myConstantTopClass.getConstantNameByValues(0))

Total code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.staticProperties
import kotlin.reflect.jvm.kotlinProperty

fun <T> KClass<*>.findConstantNameByValue(value: T): String? {
    return if (this.isKotlinObject()) {
        getConstantNameByValueForObject(this, value)
    } else {
        getConstantNameByValueFromNormalClass(this, value)
    }
}

fun <T> getConstantNameByValueFromNormalClass(kClass: KClass<*>, value: T): String? {
    value ?: return null
    return kClass.staticProperties.filter {
        it.isFinal
    }.firstOrNull() {
        it.getter.call() == value
    }?.name
}

fun <T> getConstantNameByValueForObject(kClass: KClass<*>, value: T): String? {
    value ?: return null
    return kClass.memberProperties.filter {
        it.isFinal
    }.firstOrNull {
        it.getter.call() == value
    }?.name
}

fun <T> Class<*>.getConstantNameByValues(value: T): String? {
    value ?: return null
    return declaredFields.mapNotNull {
        it.kotlinProperty
    }.filter {
        it.isFinal
    }.firstOrNull {
        it.getter.call() == value
    }?.name
}

fun KClass<*>.isKotlinObject(): Boolean {
    return this.objectInstance != null
}

Android project increases dependency (Kotlin reflection Library)

1
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

Performance issues

  • It's said that the performance of reflection is poor. Yes, but it's not so bad.
  • If you are concerned about performance, you can restrict execution to non release versions

matters needing attention

  • This method is not suitable for code obfuscation
  • Where variables are defined, the business should be single, and there should be no problem that multiple variable names correspond to one variable value

Full code

Kotlin other content recommendation

Topics: Java jvm Android github