38

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

public class mod {
    @ItemID
    public static ItemLinkTool linkTool;

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

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


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

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


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

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 中是不可能的,但谁知道......)

4

8 回答 8

24

不幸的是,没有办法做你想做的事。为什么?

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

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

正如我之前所说,一个对象可以从任何地方构造,即使是在对象没有名字的情况下。只有定义为类成员的对象才有名称(出于显而易见的访问原因):在方法中构造的对象没有。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等。拥有实施良好的重构系统。

于 2013-02-20T00:10:39.480 回答
12

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

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.

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

于 2013-02-18T20:04:56.823 回答
10

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;
}
于 2016-02-15T18:32:53.367 回答
7

这只能通过反射实现。

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

于 2013-02-18T20:03:41.533 回答
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;
}
于 2018-05-22T10:43:22.510 回答
4

如何使用lombok注释@FieldNameConstants

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

有了这个特性,我们就不需要维护获取字段名称的代码(lombok 为我们做这件事)。删除字段编译器后会抱怨它的用法。

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

于 2021-08-06T16:32:35.063 回答
1

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

我的建议是为每个重要成员放置一个静态吸气剂。例子:

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

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

于 2015-08-26T09:06:48.130 回答
0

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

于 2017-11-22T12:32:11.257 回答