kotlin函数中如何方便地获得局部变量的名字的字符串?

kotlin函数中如何获得局部变量的名字的字符串
比如函数

fun test(){
    var  a=null
    println("a")
}

//打印出 a

以上是正常的函数,以下是我想要的函数(显然,该函数会报错)

fun test(){
    var  a=null
    println(a.localVariableName)
}

//想要它成功运行并打印出a
大致思想是,想要得到一个编译器帮助,可以默认给每个局部变量添加一个字段localVariableName,该字段的数值 是程序员的局部变量的名字
有点类似于C语音中的特殊变量 __line (该变量在文件中的行号)

  1. 有一些编译器框架,但是需要显式地给每个变量添加注解
  2. 可以使用反射机制,但那样是在程序运行时,动态运行的,我更需要在编译时期就完成
阅读 729
avatarAI BotBETA
在 Kotlin 中,目前无法直接在编译时获取局部变量的名字作为字符串,并且像你所描述的那样通过 `localVariableName` 这样的属性来访问。Kotlin 编译器在编译时并不保留局部变量名的信息,因为局部变量名在编译后的字节码中通常是不重要的,它们主要用于源代码的可读性和调试。

### 解决方案

1. **使用注解和编译器插件**:
   如你提到的,有一些编译器框架和插件可以通过显式注解的方式实现类似功能。例如,Kotlin 的 KAPT(Kotlin Annotation Processing Tool)可以用于在编译时处理注解,但这通常需要为每个变量添加注解,并且需要编写额外的处理逻辑。

2. **使用字符串常量**:
   一种更简单直接的方法是使用字符串常量来代替变量名。虽然这增加了代码的冗余,但它避免了复杂的编译时处理。

fun test() {

   val aName = "a"
   var a: String? = null
   println(aName)

}


3. **调试信息和反射**:
虽然反射机制在运行时可以获取类的字段和方法信息,但它不适用于局部变量,因为局部变量在运行时通常不会以字段的形式存在。此外,反射也无法提供编译时的信息。

4. **代码生成工具**:
如果你正在使用某种构建系统或代码生成工具(如 Gradle、Maven 或 KotlinPoet),你可以编写一个插件或脚本来在编译时生成包含变量名的代码。

### 结论

由于 Kotlin 和 Java 的设计哲学,在编译时获取局部变量名的需求通常不被直接支持。如果你确实需要这种功能,可能需要考虑使用上述提到的复杂方法,或者重新评估设计需求,看是否可以通过其他方式实现相同的功能。
1 个回答

参考:Reddit - Print name and value of a variable

在 T:Any 上创建一个内联的、具体化的通用扩展函数,然后获取类内部名称,后缀为 .class 并调用 classLoader.getResource,然后使用 asm 作为类节点读取它,然后使用堆栈跟踪查找调用站点指令,然后找到其之前的 aload 指令,找到匹配的局部变量元数据,然后...打印该字符串...
@file : Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")

package io.tbontb

import jdk.internal.org.objectweb.asm.ClassReader
import jdk.internal.org.objectweb.asm.Label
import jdk.internal.org.objectweb.asm.tree.*

fun main() {
    // Test for getLocalVariableName
    val a = ""
    println(getLocalVariableName(a))
}

object Dummy

fun <T : Any> getLocalVariableName(variable: T?): String {
    // Get class/method/line that called this method
    val callSite = Thread.currentThread().stackTrace[2]
    val line = callSite.lineNumber

    // Retrieve the class as a ClassNode
    val resource = "${callSite.className.replace('.', '/')}.class"
    val stream = Dummy::class.java.classLoader.getResourceAsStream(resource)
        ?: error("Class loader is not the default system/classpath loader!")

    val node = ClassNode().also { ClassReader(stream).accept(it, 0) }
    val possibleMethods = node.methods.filter { it.name == callSite.methodName }

    // Find method that called this method
    val method = when (possibleMethods.size) {
        0 -> error("No method was found in classfile?")
        1 -> possibleMethods.first()
        else -> {
            require(line >= 0) { "Callsite debug information has been stripped, cannot find caller method overload" }
            possibleMethods.firstOrNull {
                val lines = it.lines
                val min = lines.minOfOrNull { l -> l.line }
                val max = lines.maxOfOrNull { l -> l.line }
                lines.isNotEmpty() && line in (min!!..max!!)
            } ?: error("Couldn't find matching method based on line number")
        }
    }

    // Find this method call
    val requiredLabel =
        method.lines.find { it.line == line }?.start?.label ?: error("Couldn't find target line in linenumbertable")

    var lastLabel: Label? = null
    var foundMethod: MethodInsnNode? = null

    for (insn in method.instructions) {
        when (insn) {
            is LabelNode -> lastLabel = insn.label
            is MethodInsnNode -> {
                // TODO: match owner etc
                if (lastLabel == requiredLabel && insn.name == "getLocalVariableName") {
                    // We found our method call!
                    foundMethod = insn
                    break
                }
            }
        }
    }

    if (foundMethod == null) error("Couldn't find method call insn")
    val localLoad = foundMethod.previous as? VarInsnNode ?: error("Method wasn't called with local variable as param!")

    require(localLoad.opcode in 21..25) { "Method wasn't called with local variable as param!" }

    return method.localVariables.find { it.index == localLoad.`var` }?.name
        ?: error("Local variable data was stripped for localvariabletable")
}

private val MethodNode.lines get() = instructions.filterIsInstance<LineNumberNode>()

向虚拟机传递参数,不然会抛运行时错误:

--add-exports java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED --add-exports java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED

运行结果:

a
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏