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

Java 在运行时获取成员字段源顺序的有保证的方法?,java,reflection,jvm,Java,Reflection,Jvm,我正在寻找一种方法(在运行时)以源代码顺序检索类的字段,这样我就可以根据声明的顺序执行自己的“初始化处理”。我知道类的Javadoc.getDeclaredFields()明确声明不保证订单 关于这一点的一些答案指向Javassist,但我找不到证据表明Javassist在没有行号信息的情况下有任何这样的保证 然而,Java编译器使用此“源代码顺序”,因为此代码不编译: private int a = 10 * b; private int b = 5; 显然,b的值在声明a时是未知的 这种初

我正在寻找一种方法(在运行时)以源代码顺序检索类的字段,这样我就可以根据声明的顺序执行自己的“初始化处理”。我知道
类的Javadoc.getDeclaredFields()
明确声明不保证订单

关于这一点的一些答案指向
Javassist
,但我找不到证据表明
Javassist
在没有行号信息的情况下有任何这样的保证

然而,Java编译器使用此“源代码顺序”,因为此代码不编译:

private int a = 10 * b;
private int b = 5;
显然,
b
的值在声明
a
时是未知的

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

问题:

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

  • 有没有其他保证的方法可以达到同样的效果。像Javassist这样的第三方工具是可以的,但它必须是“保证的”,或者至少是“在特定条件下保证的”

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

  • 仅供参考,我需要源代码顺序来重建顺序非常重要的遗留语言的行为。我不喜欢显式添加顺序,例如通过添加数组或注释,因为我希望尽可能保持源代码的可读性

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

    出于同样的原因,此初始化顺序也必须在运行时出现

    字段的声明顺序和初始化顺序不必相互关联。正如您所提到的,也有例外情况,但这不是必需的

    那么JVM如何按照声明的顺序初始化成员字段呢

    JVM只将字段设置为其未初始化的0、null和false值,而不设置其他值

    值具有除此之外的任何内容的唯一原因是,存在将每个字段设置为您设置的值的字节码。也就是说,不会发生奇迹

    该信息是否可以用于重建字段的源顺序

    您可以根据字段在构造函数中的设置顺序推断其顺序。但是,这是一种假设。您更有可能假设字段在类文件中的显示顺序与它们在源文件中的显示顺序相同

    如果有调试信息,这可以用来获取实际的行号,但是JVM会忽略此信息。注意:这只会告诉您字段初始化的行,而不是它们声明的顺序

    e、 g

    }


    因此,您可以看到在构造函数中初始化字段的顺序与声明的顺序不匹配。事实上,
    c
    根本不会初始化,只保留默认值
    0

    关于您的第二个和第三个问题,只有使用一种肮脏的黑客才能按顺序检索字段:

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

    如果您仍然希望对它们进行排序,我将尝试以下方法:使用字节码解析器库(如Javassist或ASM)读取类文件,并跳过除构造函数之外的所有内容(如果您还希望对静态字段进行排序,则跳过
    static{}
    )。当您遇到一条
    PUTFIELD
    PUTSTATIC
    指令,其
    owner
    是您正在检查的类时,您将通过字节码中存储的调试信息获得当前行,并使用它对字段进行排序。这种技术的问题是效率低下,而且它会在类文件中不总是存在的行号属性上进行初始化。此外,对于显式初始化的字段,您只能找到
    PUT*
    说明,例如

    protected int modifiers;
    
    没有被编译器初始化,因此字节码中没有指令,因此没有行号信息。在这种情况下,或者通常没有行号属性,很不幸,您运气不好。此时,我唯一能想到的解决方案是读取类的实际源代码


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

    如果您想在运行时检索有关编译器未为您存储的字段或方法的信息,请使用自定义注释。例如

    声明您的批注:

    @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;
    
    使用反射检索字段对象及其注释

    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?
        }
      }
    }
    
    final Class obj=MyClass.Class;
    if(对象isAnnotationPresent(FieldOrder.class)){
    对于(最终方法:obj.g
    
    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?
        }
      }
    }