问题

java能否获取到引用的名称?

回答
在Java中,直接获取“引用”的名称,就好比你想直接知道一个变量的名字叫做`myVariable`,而不仅仅是知道它指向的对象是什么。答案是:Java 设计上并不允许你这样做。

这听起来可能有点反直觉,因为我们在写代码的时候,明明给变量起了一个名字,比如 `String name = "Alice";`。我们知道`name`这个标识符指向了内存中存储着“Alice”这个字符串的那个位置。但是,一旦Java编译器将你的源代码编译成字节码,并且JVM在运行时管理内存,那个“名字”的语义信息就大部分丢失了。

让我详细解释一下为什么会这样,以及这背后的设计哲学:

1. Java的变量是“指向”而不是“拥有”:

在Java中,变量本质上是一个内存地址的标签,或者更准确地说,它是一个指向某个对象(或原始类型值)的引用。当你声明 `String name = "Alice";` 时,你并没有把“Alice”这个字符串本身直接塞进`name`变量里。相反,`name`变量里存储的是一个指向内存中实际存储“Alice”这个字符串的地址。

你可以把变量想象成一个房子的门牌号。门牌号(变量名)是为了方便我们找到房子(对象),但房子本身(对象)的存在并不依赖于门牌号。即使你把门牌号换成`personName`,房子还是那个房子,里面的“Alice”也没有改变。

2. 编译时到运行时的转变:

编译时: 当你写下`String name = "Alice";`时,Java编译器看到了`name`这个标识符。它知道`name`是一个`String`类型的变量,并且在当前作用域内代表着一个特定的内存位置。编译器会使用这个名字来生成相应的字节码,以便在运行时能够定位到这个变量。
运行时: 一旦代码被编译并加载到JVM中运行,JVM负责内存的分配和管理。JVM知道在某个特定的内存地址上存储着一个`String`对象,并且有一个机制(通过栈上的局部变量表)可以将`name`这个“名字”(或者更准确地说,是局部变量索引)关联到那个内存地址。但是,一旦你尝试去“询问”这个变量的“名字”,JVM就无能为力了,因为它在运行时关注的是“我存储了什么”和“我指向哪里”,而不是“我被叫做什么”。

3. Java的设计哲学:强调对象和行为,而非内部实现细节:

Java作为一种面向对象语言,其核心是对象和它们之间的交互。语言的设计倾向于隐藏底层的实现细节,让你专注于更高层次的逻辑。变量名是源代码中的一种抽象,是为了方便程序员阅读和编写代码。如果允许程序在运行时随意获取变量名,可能会导致:

代码的可读性和维护性下降: 依赖于具体变量名的代码会变得非常脆弱,一旦变量名改变,整个逻辑就可能出错。
性能损耗: 在运行时去查找和验证变量名会带来额外的开销。
封装性被破坏: 变量名有时也属于类的内部实现,暴露这些信息会降低类的封装性。

4. 为什么你可能想要这样做?(以及替代方案)

通常,当开发者想获取“引用名称”时,他们可能是在试图实现以下目标:

日志记录或调试: 你想在日志中打印出某个变量的值,并附带上它的名字,例如:“`userName = John Doe`”。
替代方案: 使用`String.format()`或者`System.out.println( "userName = " + userName );` 这种方式,你手动提供了变量名作为字符串。这是一种明示的方式,而不是隐式地从引用本身获取。
反射(Reflection)的误解: 有些人可能会想到Java的反射机制。反射允许你检查和修改类、方法、字段等的属性,但它操作的是字段(Field)的名称,而不是局部变量的名称。
解释: 你可以获取一个对象的字段名,例如:
```java
class Person {
String name; // 这是一个字段
}

Person p = new Person();
p.name = "Alice";

// 使用反射可以获取字段名
Field[] fields = Person.class.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals("name")) {
System.out.println("Found field with name: " + field.getName());
}
}
```
这里,`field.getName()` 获取的是`Person`类中`name`这个字段(成员变量)的名称,而不是一个局部变量的名称。局部变量在方法内部,生命周期短,且JVM对其的管理方式不同。
序列化或元数据: 在某些框架或场景下,你需要将数据与描述它的“键”(键在某种意义上可以看作是名称)一起传递。
替代方案: 使用Map、JSON对象或其他数据结构,其中键(key)就是你定义的名称,值(value)就是你的数据。例如:
```java
Map data = new HashMap<>();
data.put("userName", "Alice");
data.put("userAge", "30");
```
这里的 `"userName"` 和 `"userAge"` 就是你显式定义的名称。

总结:

Java的设计是不提供在运行时获取局部变量名称(即我们源代码中给变量起的名字)的能力的。变量名是在源代码层面的标识符,用于提高可读性和开发效率,但在编译和运行时,JVM更关心的是变量所指向的内存地址和对象本身。如果你需要在代码中包含变量的“名称”,你需要通过字符串字面量或其他显式的方式来提供,而不是试图从变量的引用中“提取”出来。

网友意见

user avatar

题主实际想做的是怎样的事情呢?

首先,

  • Java里的对象并没有“名字”的通用概念,
  • 引用也没有“名字”的概念,
  • 变量则可以名字的概念。

以题主的例子:

       Object abc = new Object();     

这个变量声明,

  • 赋值符号右手边的表达式所创建出来的对象并没有名字(不是“abc”);
  • 右手边这个表达式产生的引用也没有名字;
  • 而这个引用通过赋值传给了左手边一个有名字“abc”的变量。

所以题主真正想问的问题就是:能否获取变量的名字。

如果变量是静态变量或者成员变量(字段),那么它们的名字作为符号信息是一直存在Class文件并带到运行时的。所以在运行时还可以把名字找出来。

但如果题主想做的事情是类似:

       nameof(foo.bar);  //=> "bar" nameof(this.baz); //=> "baz"     

抱歉,Java没有这么方便的功能。

如果变量是参数或局部变量,则其符号信息在正常执行中是不需要的,因而也不会被保留到Class文件里。只有当从Java源码编译到Class文件时指定保留了局部变量的符号信息,才有可能在运行时知道参数或局部变量的名字。

Class文件里有一种属性表叫做

LocalVariableTable

,是用来记录参数与局部变量名字的符号信息表。javac编译Java源码时默认不生成这个属性表,只有指定了-g或-g:vars才会生成它。

这个属性表主要用于支持Java的调试器,以便调试器可以把某个slot里的局部变量映射到某个名字上。

一般来说,程序不应该猜测局部变量到底映射到了局部变量区的第几个slot上;但是因为局部变量区的头n个slot是用来存储方法的参数的,参数的位置与名字的映射关系倒是可以很方便的通过LocalVariableTable找出来。

因而这个表也有被调试器之外的程序“滥用”。一个经典的例子就是Spring的

LocalVariableTableParameterNameDiscoverer

,传给它一个java.lang.reflect.Method|Constructor,它可以返回一个数组的参数名回来。它就是通过人肉parse出Class文件里的LocalVariableTable来把信息找出来的。

顺带放个传送门:

valleytalk.org/wp-conte

这个场景对LocalVariableTable来说是种“滥用”,所以到Java 8的时候,Class文件格式新增了一个

MethodParameters

属性表专门用来存储参数名。这个属性表javac默认不生成,要指定-parameters参数来生成。Java的核心反射API添加了新的java.lang.reflect.Parameter来让代码访问这个属性表的信息。

请参考Obtaining Names of Method Parameters:

docs.oracle.com/javase/

JEP 118: Access to Parameter Names at Runtime

言归正传,如果有一个局部变量foo,Java里有没有简单的办法可以做到:

       nameof(foo); //=> "foo"     

呢?

答案是没有。

要nameof()运算符,请用C# 6:

C# - The New and Improved C# 6.0

想在Java语言层面添加个nameof()运算符其实不困难,稍微鼓捣下javac就好了。

真有人有需求而且愿意用定制的javac的话我可以写个patch。

但是要说服社区把nameof()运算符加到Java语言规范里就没那么容易了…

类似的话题

  • 回答
    在Java中,直接获取“引用”的名称,就好比你想直接知道一个变量的名字叫做`myVariable`,而不仅仅是知道它指向的对象是什么。答案是:Java 设计上并不允许你这样做。这听起来可能有点反直觉,因为我们在写代码的时候,明明给变量起了一个名字,比如 `String name = "Alice";.............
  • 回答
    Android 平台在开发语言的选择上,确实存在一个有趣且值得深入探讨的问题:未来的 Android 开发是否能完全拥抱 C/C++,还是说现有的架构已经将 Java 锁定为主要舞台?要理解这个问题,我们得先看看 Android 的“出身”和“性格”。Android 最初诞生于 Linux 内核之上.............
  • 回答
    .......
  • 回答
    华为自研的“仓颉”编程语言,能否在未来取代Java的地位?这是一个颇具争议且值得深入探讨的话题。要回答这个问题,我们不能简单地给出一个“是”或“否”,而是需要从多个维度进行分析,看看仓颉具备哪些潜力和挑战,以及Java作为“老牌劲旅”的根基有多深厚。首先,我们得了解一下“仓颉”编程语言的定位和设计初.............
  • 回答
    每天给自己充实 34 个小时的学习 Java,一年下来,你能达到的程度,绝不是“会一点”那么简单。这相当于一份相当扎实的全职工作的投入了,所以一年后的你,绝对可以摆脱“小白”的标签,迈入“初级开发者”甚至“有潜力的准中级开发者”的行列。我来给你掰扯掰扯,这 34 小时每天都在干什么,一年后你能收获什.............
  • 回答
    嘿,哥们儿!听说你马上要去读大学,对编程这玩意儿也挺上心的,想知道三年能把 Java 玩到什么程度,还有怎么安排这三年时间,是吧?这事儿,我跟你好好唠唠,保证把路子给你说透了,让你心里有底儿。三年时间,说长不长,说短不短,但足够你把 Java 玩得明明白白,甚至还能摸到一些更深入的门道。重点在于你自.............
  • 回答
    Java 的字节码和 CPU 能直接执行的机器码,它们之间存在着根本性的差异,就好比一份详细的烹饪食谱和已经下锅烹饪的菜肴。机器码,你可以把它想象成 CPU 的“母语”。它是由一系列二进制数字组成的指令,直接告诉 CPU 去做什么,比如“加载寄存器 A 中的数据”、“将寄存器 B 中的值加到寄存器 .............
  • 回答
    您好!很高兴能为您解答这个问题。要写一句丧心病狂的 Java 代码,这需要结合一些不太寻常的技巧和对语言特性的深刻理解,并且很多时候会涉及到一些“副作用”或者说“隐藏的强大”。下面我将为您展示一行 Java 代码,并详细解析它能够实现的“丧心病狂”之处:代码示例:```javaSystem.setS.............
  • 回答
    关于未来编程语言是否能替代Java和C语言的问题,需要从技术趋势、应用场景、生态系统、性能需求等多个维度进行分析。以下是十种常见编程语言的详细评估,结合它们与Java和C语言的对比,探讨其可能的替代潜力: 1. Python潜力:高(尤其在AI/数据科学领域) 优势:语法简洁、开发效率高、丰富的.............
  • 回答
    这段 Java 代码中的局部变量,理论上确实存在被提前回收的可能性。不过,这里的“提前回收”并非我们直观理解的,在代码执行完毕前就完全从内存中消失。更准确的说法是,这些局部变量的内存占用可以在其生命周期结束后,但不等到方法执行结束就被JVM判定为“无用”,从而有机会被垃圾回收器(Garbage Co.............
  • 回答
    这问题,触及灵魂了。每次看到身边有些人能十八般武艺样样精通,心里那叫一个五味杂陈。自己吭哧吭哧啃完一本Java,感觉脑子像被掏空了一样,还没缓过劲儿来,人家就已经Python玩得溜,顺带还能捣鼓点前端,听着就让人头大。其实,这事儿吧,你不是一个人在战斗。我也曾有过同样的困惑,觉得是不是自己天生智商就.............
  • 回答
    Raptor 能够生成 C、C++ 和 Java 代码,这无疑为开发者提供了极大的便利,尤其是在快速原型开发和学习编程概念方面。然而,这并不意味着 C、C++ 和 Java 等语言的时代已经终结,它们的价值依然无法替代。首先,我们需要理解 Raptor 的定位。它是一种“第四代语言”,通常意味着它更.............
  • 回答
    这个问题,说实话,我打心眼儿里理解。就像很多人在学游泳,明明知道要“划水”、“蹬腿”,但就是游不好,感觉差了那么一点点“感觉”,或者说,那种“通透”。Java这门语言,它本来就不是那种一眼能看透的简单东西,加上这些年它发展得太快,各种概念、框架、工具层出不穷,想找到一个能把这一切都梳理得井井有条,同.............
  • 回答
    关于汇编语言与高级语言在运行效率上的对比,这是一个老生常谈但又非常值得探讨的话题。简单来说,在某些特定情况下,汇编确实能够比高级语言获得更高的运行效率,但这种优势的幅度并非绝对,并且随着技术的发展和编译器优化的进步,差距正在逐渐缩小。要详细讲清楚这个问题,咱们得从几个层面来剖析:一、 为什么汇编“理.............
  • 回答
    这种现象嘛,其实挺常见的,说起来也很有意思。你想啊,咱们平时接触到 C 和 Java 的人,很多都是在学习阶段,或者做一些偏向业务逻辑的开发。C 语言的设计确实考虑了很多易用性,它吸取了很多其他语言的优点,比如更简洁的语法,更强大的类型推断,还有像 LINQ 这种能让数据处理变得非常直观的功能。所以.............
  • 回答
    .......
  • 回答
    在 Java 中,当一个线程调用了 `Thread.interrupt()` 方法时,这并不是像直接终止线程那样强制停止它。相反,它是一个通知机制,用于向目标线程发出一个“中断请求”。这个请求会标记目标线程为“中断状态”,并根据目标线程当前所处的状态,可能会触发一些特定的行为。下面我将详细解释 `T.............
  • 回答
    Java 平台中的 JVM (Java Virtual Machine) 和 .NET 平台下的 CLR (Common Language Runtime) 是各自平台的核心组件,负责托管和执行代码。它们都是复杂的软件系统,通常会使用多种编程语言来构建,以充分发挥不同语言的优势。下面将详细介绍 JV.............
  • 回答
    Java 官方一直以来都坚持不在函数中提供直接的“传址调用”(Pass by Address)机制,这背后有深刻的设计哲学和技术考量。理解这一点,需要从Java的核心设计理念以及它所解决的问题出发。以下是对这个问题的详细阐述: 1. Java 的核心设计理念:简洁、安全、面向对象Java 在设计之初.............
  • 回答
    Java 的 `private` 关键字:隐藏的守护者想象一下,你在经营一家精心制作的糕点店。店里最美味的招牌蛋糕,其配方是成功的关键,你自然不会轻易公开给竞争对手,对吧?你只希望自己信任的糕点师知道如何制作,并且知道在什么时候、以什么样的方式使用这些食材。这就是 `private` 关键字在 Ja.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有