java - 在运行时获取成员字段的源顺序的保证方法?

标签 java reflection jvm

我正在寻找一种方法(在运行时)按源代码顺序检索类的字段,以便我可以执行我自己的“初始化处理”,这将基于声明的顺序。我知道 Class.getDeclaredFields() 的 Javadoc 明确指出不保证任何顺序。

SO 上的一些答案指向 Javassist 但我找不到任何证据表明 javassist 在没有行号信息的情况下有任何此类保证。

然而,这个“源代码顺序”被 Java 编译器使用,因为这段代码无法编译:

private int a = 10 * b;
private int b = 5;

显然,b 的值在声明 a 时是未知的。

这个初始化顺序也必须出现在字节码中,因为在运行时初始化必须以相同的顺序发生(当然,这只是这些边缘情况的要求:-(然而这让我认为自然的事情会是将源代码顺序存储在 .class 文件中。

问题:

  1. JVM/字节码如何按照声明的顺序初始化成员字段,这些信息是否可以用于重构字段的源顺序?

  2. 是否有任何其他有保证的方法可以实现同样的目标。像 Javassist 这样的第三方工具是可以的,但必须“保证”或至少“在特定条件下保证”。

  3. 是否有任何特定的 Java 实现可以保证 Class.getDeclaredFields() 的顺序(可能在特定条件下(哪些))?

供您引用,我需要源代码顺序来重建顺序很重要的遗留语言的行为。我不喜欢明确地添加订单,例如通过添加数组或注释,因为我希望尽可能保持源代码的可读性。

-- 编辑-- 一个重要的注意事项可能是我需要“遍历”的字段都将被注释,例如@MyComplexType(len = 4)。父类需要这个元信息来构造一种内存映射。然而,我不想将此注释与订购信息混为一谈,因为我发现这会妨碍可读性和可维护性。

最佳答案

关于你的第二个和第三个问题,只能使用一种肮脏的 hack 按顺序检索字段:

在字节码中,类文件的字段没有按顺序存储,方法也没有。我不知道为什么是这种情况(即使我制作了自己的 JVM 编译器),但我相信 Java 编译器只是决定这样做。 Class.getDeclaredFields 按照从字节码中读取字段的顺序返回字段,这就是为什么它声明不保证顺序的原因。

如果您仍想按顺序排列它们,我会尝试以下方法:您使用字节码解析器库(如 Javassist 或 ASM)来读取类文件,并跳过除构造函数(和 static { } 如果您还想对静态字段进行排序)。一旦遇到 PUTFIELDPUTSTATIC 指令,其 owner 是您正在检查的类,您将获得通过调试可用的当前行存储在字节码中的信息,并用它来对字段进行排序。这种技术的问题是它的效率低下,而且它依赖于行号属性,而这些属性并不总是出现在类文件中。此外,您只会为显式初始化的字段找到 PUT* 指令,默认字段如

protected int modifiers;

没有被编译器初始化,所以字节码中没有可用的指令和行号信息。在这种情况下或通常没有 LineNumber 属性时,不幸的是你运气不好。那时,我唯一能想到的解决办法就是阅读该类的实际源代码。

根据您尝试检查的类,您可能无法获取类的实际字节码,但这本身就是一个问题。

关于java - 在运行时获取成员字段的源顺序的保证方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31046971/

相关文章:

java - 即使我限制了最大堆空间大小,JVM 如何进行分页调入/调出?

Java 正则表达式 ReplaceAll 与分组

java - 正则表达式在最后一次出现大于符号(>)/任何特殊字符后提取值并删除数字

java - 使用大量 boolean 开关的替代方法是什么

c# - 无法获取 PropertyInfo.SetValue() 来设置我的对象的值

java - 研究消除堆空间问题?

Java Swing : Clear JList without triggering listener

java - 在运行时指定通用集合类型参数(Java 反射)

go - 如何列出接口(interface)类型中的方法名称?

java - JVM 的 "-server"选项是否需要成为第一个选项?