【问题标题】:Get name of a field获取字段名称
【发布时间】:2013-02-03 08:35:06
【问题描述】:

在 Java 中是否可以从实际字段中获取字符串中的字段名称?喜欢:

public class mod {
    @ItemID
    public static ItemLinkTool linkTool;

    public void xxx{
        String fieldsName = *getFieldsName(linkTool)*;
    }
}

PS:我不是在寻找字段的类/类名或从 String 中的名称获取 Field。


编辑: 当我查看它时,我可能不需要获取字段名称的方法,Field 实例(来自字段的“代号”)就足够了。 [例如。 Field myField = getField(linkTool)]

Java 本身可能没有我想要的东西。我会看一下 ASM 库,但最后我可能会使用字符串作为字段的标识符:/


编辑2: 我的英语不是很好(但即使用我的母语我也很难解释这一点),所以我再添加一个例子。希望现在会更清楚:

public class mod2 {
    @ItemID
    public static ItemLinkTool linkTool;

    @ItemID
    public static ItemLinkTool linkTool2;

    @ItemID
    public static ItemPipeWrench pipeWrench;

    public void constructItems() {
        // most trivial way
        linkTool = new ItemLinkTool(getId("linkTool"));
        linkTool2 = new ItemLinkTool(getId("linkTool2"));
        pipeWrench = new ItemPipeWrench(getId("pipeWrench"));

        // or when constructItem would directly write into field just
        constructItem("linkTool");
        constructItem("linkTool2");
        constructItem("pipeWrench");

        // but I'd like to be able to have it like this
        constructItemIdeal(linkTool);
        constructItemIdeal(linkTool2);
        constructItemIdeal(pipeWrench);
    }

    // not tested, just example of how I see it
    private void constructItem(String name){
        Field f = getClass().getField(name);
        int id = getId(name);

        // this could be rewritten if constructors take same parameters
        // to create a new instance using reflection
        if (f.getDeclaringClass() == ItemLinkTool){
            f.set(null, new ItemLinkTool(id));
        }else{
            f.set(null, new ItemPipeWrench(id));
        }
    }
}

问题是:constructItemIdeal 方法怎么看? (根据答案和谷歌搜索,我认为这在 Java 中是不可能的,但谁知道......)

【问题讨论】:

  • 如果字段不是私有的,那么可以从代码的特定部分知道被引用的字段的名称,但分析字节码。您可以为此使用 ASM 库。
  • 字段名称是什么意思?你的例子中是那个字符串“linkTool”吗?
  • 您是指您自己编写的类的字段名称还是其他类的名称?
  • 请注意,ItemLinkTool 可能不止一种方法。在这种情况下,您的 getFieldsName 方法应该返回什么?
  • 我不确定我是否写得很清楚,我想要与以下相同的功能: this.getClass().getField("linkTool") 只是没有字符串部分 ~ this.getClass() .getField(linkTool).

标签: java reflection field


【解决方案1】:

如何使用lombok注解@FieldNameConstants

然后您可以通过以下方式访问您的字段名称: mod.Field.linkTool

这样我们就不需要维护获取字段名称的代码(lombok 为我们做的),当我们删除该字段时,编译器会抱怨它的用法。

请注意,此功能已标记为实验性。

Reference link

【讨论】:

    【解决方案2】:

    我想这就是你要找的。​​p>

    SomeClass data; // or List<SomeClass> data; etc.
    List<String> fieldNames = getFieldNamesForClass(data.get(0).getClass());
    
    private static List<String> getFieldNamesForClass(Class<?> clazz) throws Exception {
        List<String> fieldNames = new ArrayList<String>();
        Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            fieldNames.add(fields[i].getName());
        }
        return fieldNames;
    }
    

    【讨论】:

      【解决方案3】:

      可以使用运行时字节码检测来完成,例如使用 Byte Buddy 库,请参阅此答案nameof equivalent in Java

      【讨论】:

        【解决方案4】:

        String fieldName = getFieldName(linkTool, this);

        private static String getFieldName(Object fieldObject, Object parent) {
        
            java.lang.reflect.Field[] allFields = parent.getClass().getFields();
            for (java.lang.reflect.Field field : allFields) {
                Object currentFieldObject;
                try {
                    currentFieldObject = field.get(parent);
                } catch (Exception e) {
                    return null;
                }
                boolean isWantedField = fieldObject.equals(currentFieldObject);
                if (isWantedField) {
                    String fieldName = field.getName();
                    return fieldName;
                }
            }
            return null;
        }
        

        【讨论】:

        • 如果同一个对象实例被分配给多个字段,这将是不可靠的。
        • 对于这种特殊情况,您必须返回字段名称列表。
        【解决方案5】:

        我获取字段名称的动机是使用它来搜索键值数据库(如 mongodb)中的内容。我有一个结构,其中包含代表已保存数据的成员(字段)。 我使用成员名称搜索数据库

        我的建议是为每个重要成员放置一个静态 getter。示例:

        public class Data {
          public static String getMember1Key() {
            return "member1";
          }
          public String member1;
        }
        

        希望 Java 将来会为此提供一些机制,因为如今,元数据可能是数据的一部分。尤其是在使用 JSON 做事时。

        【讨论】:

        • 答案在哪里?
        【解决方案6】:

        不幸的是,没有办法如你所愿。 为什么?

        在我们仍在努力证明 Java 并不慢的时代,存储对象构造的位置或方式的元数据会产生很大的开销,而且由于它的使用范围非常小,因此不存在.不仅如此:还有很多技术原因。

        一个原因是因为 JVM 是如何实现的。 Java class 文件格式的规范可以在here 中找到。这是一口大口,但信息量很大。

        正如我之前所说,可以从任何地方构造对象,即使是在对象没有名称的情况。只有定义为类成员的对象才有名称(出于显而易见的访问原因):在方法中构造的对象没有。 JVM 有一个本地变量表,最多有 65535 个条目。这些被加载到堆栈并通过 *load 和 *store 操作码存储到表中。

        换句话说,像这样的类

        public class Test {
        int class_member = 42;
        
           public Test() {
              int my_local_field = 42;
           }
        }
        

        被编译成

        public class Test extends java.lang.Object
          SourceFile: "Test.java"
          minor version: 0
          major version: 50
          Constant pool:
          --snip--
        
        {
        int class_member;
        
        public Test();
          Code:
           Stack=2, Locals=2, Args_size=1
           0:   aload_0
           1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
           4:   aload_0
           5:   bipush  42
           7:   putfield        #2; //Field class_member:I
           10:  bipush  42
           12:  istore_1
           13:  return
          LineNumberTable:
           --snip--    
        }
        

        在那里,您可以从javap -v Test 中看到一个清晰的示例,虽然名称class_member 被保留,但my_local_field 已被抽象为局部变量表中的索引1(0 为this 保留)。

        这本身对调试器等来说是一种痛苦,因此设计了一组属性(想想类文件的元数据)。其中包括LocalVariableTableLineNumberTableLocalVariableTypeTable。这些属性对于堆栈跟踪(LineNumberTable)和调试器(LocalVariableTable 和 LocalVariableTypeTable)很有用。但是,这些是完全可选择包含在文件中的,并且不能保证它们是:

        LineNumberTable 属性是可选的可变长度属性 在属性表中

        其他人也是如此。

        此外,大多数编译器默认情况下只生成 LineNumberTable 以外的任何内容,因此即使有可能,您也会很不走运。

        基本上,对于开发人员来说,拥有getFieldName(object)(这对于局部变量来说首先是不可能的)是非常令人沮丧的,并且只有在这些属性存在时才能工作。

        因此,在可预见的未来,您将无法使用带有字符串的 getField。无耻的插件:IntelliJ 似乎很好地处理带有反射的重构:我也编写了 Minecraft 模组,我知道这种感觉,并且可以说 IntelliJ 显着减少了重构工作。但对于大多数现代 IDE 来说可能也是如此:我确信 Eclipse、Netbeans 等大玩家都有实施良好的重构系统。

        【讨论】:

        • 查看我的答案以获取字段名称。
        • 为什么在编译过程中将字段名称替换为字符串文字会“慢”?
        【解决方案7】:

        如果该字段是私有的,您可以使其可访问:

        Field field = object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        String value = (String)field.get(object); // to get the value, if needed. In the example is string type
        

        要获取字段的名称,请这样做:

        //use the field object from the example above
        field.getName(); // it is in String.
        

        如果您不知道该字段的名称...好吧..那么您想获得哪个字段? :) 您可以使用反射获取所有字段,然后循环遍历它们。但是如果您不知道该字段,那么此操作的实际意义和逻辑是什么?

        【讨论】:

        • 这就是问题所在,在输入时我希望链接工具不是“链接工具”。
        • 好吧,你没有得到引号中的名称。有什么问题?
        • 大量使用包含字段/类/方法/命名空间标识符的字符串的代码不容易维护或重构(通常需要手动操作,IDE 存在此类问题)。跨度>
        • 我试过代码:Field field = this.getClass().getDeclaredField(field1); field.setAccessible(true);返回字段.getName();在 POJO 类本身,但我得到 NullPointer。 field1 是“私有字符串 field1”
        • 调用 object.getClass().getDeclaredField(fieldName) 违背了问题的目的,即从对象本身获取字段的名称。此调用中的参数“fieldName”将与 (String)field.get(object) 返回的值相同。根据问题,“fieldName”是未知的。
        【解决方案8】:

        这只能通过反射实现。

        使用Class.getDeclaringFields() 和java.reflection.Field.getName()

        【讨论】:

        • 是的,但是使用反射我需要字符串中的字段名称作为参数(真实类中有更多字段,它们也有不同的类型)。我的问题是,是否有一种方法可以从代码中的字段名称(而不是字符串)中获取 java.lang.reflect.Field 类型的实例。
        • 是的,使用反射,获取所有声明字段,遍历该数组,第一个与您的文件名匹配的就是正确的。
        • 如何从代码中的字段名称中获取字符串中的字段名称?看看新的例子,也许这次我写的问题更好。
        • @AlexWien Class.getDeclaringFields() 中没有这样的方法,答案是无法理解的。
        • @ShashiRanjan 试试 your-class-name.class.getDeclaredFields()。这将返回一个 java.reflection.Field 类型的字段数组,您可以对其进行迭代并应用 .getName() 方法。
        猜你喜欢
        • 2021-08-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多