如何在Java中循环遍历类属性?

111

我该如何在 Java 中动态遍历类属性?

例如:

public class MyClass{
    private type1 att1;
    private type2 att2;
    ...

    public void function(){
        for(var in MyClass.Attributes){
            System.out.println(var.class);
        }
    }
}

在Java中是否有可能实现这个?

7个回答

113

没有语言支持来做你所要求的。

你可以使用反射在运行时反射访问类型的成员(例如使用 Class.getDeclaredFields() 获取一个 Field 数组),但根据你所需做的具体情况,这可能不是最好的解决方案。

另请参阅

相关问题


示例

这是一个简单的示例,展示了反射能够做到的一部分。

import java.lang.reflect.*;

public class DumpFields {
    public static void main(String[] args) {
        inspect(String.class);
    }
    static <T> void inspect(Class<T> klazz) {
        Field[] fields = klazz.getDeclaredFields();
        System.out.printf("%d fields:%n", fields.length);
        for (Field field : fields) {
            System.out.printf("%s %s %s%n",
                Modifier.toString(field.getModifiers()),
                field.getType().getSimpleName(),
                field.getName()
            );
        }
    }
}

上面的片段使用反射来检查String类的所有声明字段;它会产生以下输出:

7 fields:
private final char[] value
private final int offset
private final int count
private int hash
private static final long serialVersionUID
private static final ObjectStreamField[] serialPersistentFields
public static final Comparator CASE_INSENSITIVE_ORDER

《Effective Java》第二版,条款53:优先使用接口而不是反射

以下摘自该书:

通过 Class 对象,可以获取代表类的构造函数、方法和字段的 ConstructorMethodField 实例,并以反射方式操纵它们的底层对应物。然而,这种能力是有代价的:

  • 您失去了所有编译时检查的好处。
  • 执行反射访问所需的代码笨拙而冗长。
  • 性能受损。

通常情况下,在正常的应用程序运行时,不应以反射方式访问对象。

有一些复杂的应用程序需要使用反射,例如[...故意省略...]。如果您对您的应用程序是否属于这些类别有任何疑问,那么它可能不属于这些类别。


实际上,根据字段的值,我想要写入或不写入每个字段的值? - Zakaria
@Zakaria:是的,你可以用反射来完成这个任务,但根据你所要做的事情,可能会有更好的设计方案。 - polygenelubricants
@Zakaria:除非你需要一个通用的高度可定制的班级报告生成器,可以与_任何_班级一起使用,否则你应该远离反射,只需为应用程序域中的此特定类编写一个特定的解决方案。 - polygenelubricants
1
@Zakaria:那就试试反射吧。有Field.get可以用来通过反射读取字段的值。如果是private,你可以使用setAccessible绕过它。是的,反射非常强大,它让你可以做一些事情,比如检查private字段、覆盖final字段、调用private构造函数等等。如果你需要更多关于设计方面的帮助,最好写一个新问题并提供更多信息。也许你在第一次中根本不需要这么多属性(例如使用enumList等)。 - polygenelubricants
1
泛型在这里没有用处。只需执行 static void inspect(Class<?> klazz) - user102008
显示剩余5条评论

49

在Java中,直接访问字段不是很好的风格。我建议为您的bean创建getter和setter方法,然后使用java.beans包中的Introspector和BeanInfo类。

MyBean bean = new MyBean();
BeanInfo beanInfo = Introspector.getBeanInfo(MyBean.class);
for (PropertyDescriptor propertyDesc : beanInfo.getPropertyDescriptors()) {
    String propertyName = propertyDesc.getName();
    Object value = propertyDesc.getReadMethod().invoke(bean);
}

1
有没有办法只获取bean的属性,而不是类?即避免getPropertyDescriptors返回MyBean.class。 - hbprotoss
如果我理解正确的话,你可以检查 propertyDesc.getReadMethod().getDeclaringClass() != Object.class,或者你可以指定一个类作为第二个参数来停止分析,例如 getBeanInfo(MyBean.class, Object.class) - Jörn Horstmann
我对这个解决方案的问题在于,getter(propertyDesc)是按字母顺序排序的,而我需要字段/ getter 的原始顺序。 - salerokada

30
虽然我同意Jörn的回答,如果你的类符合JavaBeans规范,但如果不符合并且你使用Spring,这里有一个很好的替代方案。
Spring有一个名为ReflectionUtils的类,提供了一些非常强大的功能,包括doWithFields(class, callback)这个方法,它是一种访问者模式的方法,可以使用回调对象来迭代一个类的字段,就像这样:
public void analyze(Object obj){
    ReflectionUtils.doWithFields(obj.getClass(), field -> {

        System.out.println("Field name: " + field.getName());
        field.setAccessible(true);
        System.out.println("Field value: "+ field.get(obj));

    });
}

但是这里有一个警告:这个课程被标记为“仅限内部使用”,如果你问我,这真是可惜。

19

迭代类字段并获取对象值的简单方法:

 Class<?> c = obj.getClass();
 Field[] fields = c.getDeclaredFields();
 Map<String, Object> temp = new HashMap<String, Object>();

 for( Field field : fields ){
      try {
           temp.put(field.getName().toString(), field.get(obj));
      } catch (IllegalArgumentException e1) {
      } catch (IllegalAccessException e1) {
      }
 }

一个类或者任何对象。 - Rickey
@Rickey 有没有办法通过循环每个字段来设置属性的新值? - QWERTY
@hyperfkcb 当您尝试更改您正在迭代的属性/字段的值时,可能会出现修改异常。 - Rickey

6

Java有反射(java.reflection.*),但我建议使用像Apache Beanutils这样的库,它会使过程比直接使用反射更加简单。


0
可以使用pojo-analyzers,这样还可以访问字段的getter和setter(无需反射)。
@DetailedPojo
public class ClassWithStuff {
    public String someString;
    //many more fields
}
...
ClassWithStuff classWithStuff = new ClassWithStuff("myString");
// Pojo Analyzers will automatically create a class named DetailedClassWithStuff
DetailedClassWithStuff.list.stream()
    .forEach(fieldDetails -> 
            System.out.println(fieldDetails.getFieldValue(classWithStuff)); // "myString"

图书馆使用注解处理,因此不涉及反射-这是可能的最快解决方案。

0

这里有一个解决方案,它按字母顺序对属性进行排序,并将它们的值一起打印出来:

public void logProperties() throws IllegalArgumentException, IllegalAccessException {
  Class<?> aClass = this.getClass();
  Field[] declaredFields = aClass.getDeclaredFields();
  Map<String, String> logEntries = new HashMap<>();

  for (Field field : declaredFields) {
    field.setAccessible(true);

    Object[] arguments = new Object[]{
      field.getName(),
      field.getType().getSimpleName(),
      String.valueOf(field.get(this))
    };

    String template = "- Property: {0} (Type: {1}, Value: {2})";
    String logMessage = System.getProperty("line.separator")
            + MessageFormat.format(template, arguments);

    logEntries.put(field.getName(), logMessage);
  }

  SortedSet<String> sortedLog = new TreeSet<>(logEntries.keySet());

  StringBuilder sb = new StringBuilder("Class properties:");

  Iterator<String> it = sortedLog.iterator();
  while (it.hasNext()) {
    String key = it.next();
    sb.append(logEntries.get(key));
  }

  System.out.println(sb.toString());
}

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