获取字段名称

44

在Java中是否能够从实际字段获取字段名称的字符串,例如:

public class mod {
    @ItemID
    public static ItemLinkTool linkTool;

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

PS:我不是在寻找一个字段的类/类名或从字符串中获取字段的名称。


编辑: 当我查看时,我可能不需要一个方法来获取字段的名称,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库来实现此功能。 - Mikhail Vladimirov
1
你说的字段名是什么意思?你的示例中是指字符串“linkTool”吗? - AlexWien
你是指你自己编写的类的字段名称还是另一个类? - jlordo
我不确定我是否表达清楚,我想要的功能与 this.getClass().getField("linkTool") 相同,只是没有字符串部分 ~ this.getClass().getField(linkTool)。 - monnef
我获取字段名称的动机是为了在键值数据库(如mongodb)中搜索内容。我有一个包含成员(字段)的结构体,代表着已保存的数据。我使用成员名称来搜索数据库 - AlikElzin-kilaka
显示剩余4条评论
9个回答

27

很遗憾,无法实现你所希望的功能。为什么?

在一个仍在努力证明Java不慢的时代,存储对象构造的位置或方式的元数据会产生巨大的开销,并且由于其极小的使用范围,它并不存在。此外,还有一些技术上的原因。

其中一个原因是JVM的实现方式。Java class文件格式的规范可以在这里找到。这个规范非常详细,但也很繁琐。

正如我之前提到的,一个对象可以从任何地方构造,甚至是没有名字的对象的情况下。只有被定义为类成员的对象才有名字(很显然是为了访问而命名):在方法中构造的对象则没有名字。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:我相信主流 IDEs,如 Eclipse、Netbeans 等都拥有良好实现的重构系统。


查看我的答案以获取字段名称。 - Stefan
在编译期间将字段名称替换为字符串文字,为什么会变得“慢”? - Orestis P.
不确定为什么会很慢。C#有关键字“nameof”,它在编译时进行评估。它非常有用,可以通过IntelliSense获得可靠的代码使用情况(因此您可以轻松重命名参数,并且它将自动更新到所有位置)。 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof - oscfri

17

不如试试使用 lombok 注解 @FieldNameConstants

@FieldNameConstants
public class Mod {
    public ItemLinkTool linkTool;
}
...
String fieldName = Mod.Fields.linkTool; //fieldName == "linkTool"

有了这个功能,我们就不需要维护获取字段名称的代码了(lombok已经替我们实现了)。在删除字段后,编译器会报告它的使用情况。

请注意,该功能被标记为实验性的。


3
谢谢,我不知道Lombok也提供这个功能! - Michele

17
如果该字段是私有的,您可以使其可访问:
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.

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


那就是问题所在,我想输入的是 linkTool 而不是 "linkTool"。 - monnef
好的,你不需要在引号中输入名称。问题出在哪里? - user
6
大量使用包含字段、类、方法和命名空间标识符的字符串编码不易于维护或重构(通常需要手动操作,IDE 对此类事情存在问题)。 - monnef
我尝试在POJO类中使用以下代码:Field field = this.getClass().getDeclaredField(field1); field.setAccessible(true); return field.getName(); 但是我得到了NullPointer异常。其中field1是"private String field1"。 - Shashi Ranjan
1
调用object.getClass().getDeclaredField(fieldName)会破坏问题的目的,即从对象本身获取字段名称。在此调用中,“fieldName”参数将与(String)field.get(object)返回的值相同。根据问题,“fieldName”是未知的。 - Monte Creasor

10

使用getFieldName(linkTool, this)方法获取字段名fieldName。

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;
}

2
如果将同一对象实例分配给多个字段,则这将是不可靠的。 - Giovanni Lovato
对于那种特殊情况,您需要返回一个字段名称列表。 - Stefan

7

我认为这就是你在寻找的东西。

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;
}

7

只有通过反射才能实现这一点。

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


5
是的,但是使用反射时我需要将参数作为字符串传递字段名称(实际类中有更多字段,它们也具有不同的类型)。我的问题是是否有一种方法可以通过代码中字段名称的字符串获得java.lang.reflect.Field类型的实例(而非从字符串获取)。 - monnef
是的,使用反射获取所有声明字段,遍历该数组并找到与您的文件名匹配的第一个字段即为正确的字段。 - AlexWien
如何从代码中的字段名称获取字符串中的字段名称?看看新的示例,也许这次我写得更好了。 - monnef
@AlexWien,在Class.getDeclaringFields()中没有这样的方法,回答不可理解。 - Shashi Ranjan
2
@ShashiRanjan 尝试使用 your-class-name.class.getDeclaredFields()。这将返回一个类型为 java.reflection.Field 的字段数组,您可以对其进行迭代并应用 .getName() 方法。 - GHOST-34

1
我的动机是获取字段名,以便在键值数据库(如mongodb)中搜索内容。我有一个包含成员(字段)的结构体,用于表示保存的数据。我使用成员名称来搜索数据库
我的建议是为每个重要的成员添加一个静态getter。例如:
public class Data {
  public static String getMember1Key() {
    return "member1";
  }
  public String member1;
}

希望Java未来能够有一些机制来处理这个问题,因为现在元数据可能是数据的一部分,特别是在处理JSON时。

答案在哪里? - Shashi Ranjan

0

除了Lombok的@FieldNameConstants之外,还可以使用{{link1:pojo-analyzers}},这也将使得可以访问字段的getter和setter(无需反射)。

@DetailedPojo
public class Mod {
    public ItemLinkTool linkTool;
}
...
Mod mod = new Mod(myItemLinkTool);
DetailedMod.map.get("entityId").getFieldValue(mod); // myItemLinkTool

0

可以使用运行时字节码插装来完成,例如使用Byte Buddy库,参见此答案Java中的等效名称


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接