我正在寻找一种方法来检索(在运行时)源顺序中类的字段,以便我可以执行自己的“初始化处理”,这将基于声明的顺序。我知道Class.getDeclaredFields()
的Javadoc明确声明没有订单得到保证。
SO上的一些答案指向Javassist
,但我没有发现javassist
在没有行号信息的情况下有任何此类保证的证据。
然而,这个“源顺序”由Java编译器使用,因为此代码无法编译:
private int a = 10 * b;
private int b = 5;
显然,b
的价值在宣布a
时尚不清楚。
这个初始化顺序也必须存在于字节码中,因为在运行时初始化必须以相同的顺序发生(授予,这只是这些边缘情况的要求:-(但这让我认为自然的事情会将源订单存储在.class
文件中。
问题:
JVM /字节代码如何按声明的顺序初始化成员字段,这些信息是否可用于重建字段的源顺序?
还有其他保证的方法可以达到同样的效果。像Javassist这样的第三方工具可以,但必须“保证”或至少“在特定条件下保证”。
是否有任何特定的Java实现确保Class.getDeclaredFields()
的订单(可能在特定条件下(哪些))?
为了您的信息,我需要源订单来重建订单很重要的遗留语言的行为。我不喜欢明确添加订单,例如通过添加数组或注释,因为我希望尽可能保持源的可读性。
- 编辑 -
一个重要的注意事项可能是我需要“遍历”的字段都将被注释,例如, @MyComplexType(len = 4)
。父类将需要此元信息来构造一种内存映射。然而,我不想用订购信息来混淆这个注释,因为我发现这会妨碍可读性和可维护性。
答案 0 :(得分:2)
出于同样的原因,此初始化顺序也必须在运行时出现。
声明字段的顺序及其初始化顺序不必彼此有任何关系。如您所述,有一些例外,但这不是必需的。
那么JVM如何以声明的顺序初始化成员字段
JVM仅将字段设置为未初始化的0,null和false值。没别了。
值之外的唯一原因是因为有字节代码将每个字段设置为您设置的值。即没有魔法发生。
这些信息可能用于重建字段的源顺序吗?
您可以根据构造函数中字段的设置顺序推断出订单的内容。但是,这是一个假设。您更有可能假设字段在类文件中出现的顺序是它们在源文件中出现的顺序。
如果存在调试信息,则可以使用此信息获取实际行号,但JVM会忽略此信息。注意:这只会告诉您初始化字段的行,这可能不是它们被声明的顺序。
e.g。
class A {
int a;
int b;
int c;
int d;
A() {
d = 1;
//c not initialised
b = 2;
a = 3;
}
}
因此,您可以看到构造函数中字段初始化的顺序与声明的顺序不匹配。事实上c
根本不会被初始化,而是保留默认的0
答案 1 :(得分:2)
关于你的第二个和第三个问题,只能使用一种肮脏的黑客来检索字段:
在字节码中,类文件的字段不按顺序存储,方法也不存在。我不知道为什么就是这种情况(即使我自己创建了JVM编译器),但我相信Java编译器决定这样做。 Class.getDeclaredFields
按照从字节码中读取的顺序返回字段,这就是为什么它声明没有订单得到保证。
如果您仍想按顺序获取它们,我会尝试以下操作:您使用字节码解析器库(如Javassist或ASM)来读取类文件,并跳过除构造函数之外的所有内容(以及static {}
如果你还想对静态字段进行排序)。一旦遇到PUTFIELD
或PUTSTATIC
指令,其owner
是您正在检查的类,您将获得当前行,该行可通过存储在字节码中的调试信息获得,并使用它对字段进行排序。这种技术的问题在于它效率低下,而且它依赖于行号属性,而这些属性并不总是出现在类文件中。此外,您只会找到明确初始化字段的PUT*
条指令,默认情况下为
protected int modifiers;
未由编译器初始化,因此字节码中没有指令,因此没有行号信息。在这种情况下或者一般没有LineNumber属性时,遗憾的是你不幸。那时,我能想出的唯一解决方案是阅读该类的实际源代码。
根据您要检查的类,您可能无法获取该类的实际字节码,但这本身就是一个问题。
答案 2 :(得分:0)
如果要在运行时检索有关编译器未为您存储的字段或方法的信息,请使用自定义注释。例如,
声明你的注释:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldOrder {
public int order() default 0;
}
为您的字段添加注释:
@FieldOrder{order=1}
int field1 = 5;
@FieldOrder{order=2}
long field2 = field1 * 12;
使用反射检索Field对象及其上的注释。
final Class<MyClass> obj = MyClass.class;
if (obj.isAnnotationPresent(FieldOrder.class)) {
for (final Method method : obj.getDeclaredMethods()) {
if (method.isAnnotationPresent(FieldOrder.class)) {
final Annotation annotation = method.getAnnotation(FieldOrder.class);
final FieldOrder fieldOrder = (FieldOrder) annotation;
final int order = fieldOrder.order();
// do something with the field;
// add to sorted collection using field order?
}
}
}