21

巧用Kotlin反射实现按值取名,调试做到多快好省 - 技术小黑屋

 3 years ago
source link: https://droidyue.com/blog/2020/05/31/using-kotlin-reflection-to-get-constant-variable-name-by-value/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

巧用Kotlin反射实现按值取名,调试做到多快好省

May 31st, 2020

我们经常会定义一些常量,比如

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;
}

当我们打印查看是哪种类型的时候,如果单纯是打印int值,并不能足够解释业务信息,所以,为了更好的输出信息,我们通常会这样做

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")
}

然后这样调用

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

这样做能打印出更加有意义的信息,但是需要编写额外的将int转换成String的方法,可谓是费时费力。

有没有好办法

方法是有的。

比如我们定义public static final int TYPE_TEXT = 0;的时候,我们定义了常量名和常量值。那么

  • 我们可以利用变量值查找对应的变量名
  • 借助 Kotlin便捷的特性和反射库,我们可以更好更轻松实现。

针对 Java 类(接口)和普通的 Kotlin类

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
}
1
println("itemType=${ItemType::class.getConstantNameByValueFromNormalClass(itemType)}")

针对 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
}
1
2
3
4
5
6
7
8
//定义常量在Object对象中
object ErrorCodes {
    const val ERROR_OK = 0
    const val ERROR_INVALID_PARAM = 1
}

//调用处
println("errorCode=" + ErrorCodes::class.getConstantNameByValueForObject(0))

针对 Kotlin Top Level 变量的问题

  • 对于 Kotlin变量定义,我们推荐是定义在top level层级。
  • 但是 Kotlin无法直接访问到 top level 层级的类
  • 我们需要借助一些变量来辅助获取 top level 层级的类
  • 但是 Kotlin的反射无法top level类,所以我们必须使用java class

借助一个变量或者顶层类

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

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

借助 Java class来获取常量(用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
}
1
println("sourceType=" + myConstantTopClass.getConstantNameByValues(0))
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 工程增加依赖(Kotlin 反射库)

1
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
  • 都说,反射的话性能比较差,是的,但是也不是那么的差。
  • 如果担心性能,可以限定在非release版本下执行
  • 这种方法不适合于代码混淆后进行处理
  • 定义变量的地方,保持业务单一,不要出现多个变量名对应一个变量值的问题

Kotlin其他内容推荐


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK