【问题标题】:Setting Bean's properties with a loop使用循环设置 Bean 的属性
【发布时间】:2015-07-30 13:27:30
【问题描述】:

我有一个名为 Bean 的类,它有 3 个字段

public class Bean {

private Object field0;
private Object field1;
private Object field2;

public Object getField0() {
    return field0;
}

public void setField0(Object field0) {
    this.field0 = field0;
}

public Object getField1() {
    return field1;
}

public void setField1(Object field1) {
    this.field1 = field1;
}

public Object getField2() {
    return field2;
}

public void setField2(Object field2) {
    this.field2 = field2;
}

我想为每个字段设置数据来这样做

    int j, i;

    for (j = 0; j < body.size(); j++) {
        line = new Bean();
        List row = body.get(j);

        HashMap map = new HashMap(headers.length);

        for (i = 0; i < headers.length; i++) {

            line.choosefield2(i, headers, row);
        }
        list.add(line);
    }

并且choosefield2在bean中:

public void choosefield2(int i, String[] headers, List row) {

    switch (i) {

    case 0:
        this.setField0(row.get(0));
        break;

    case 1:
        this.setField1(row.get(1));
        break;

    case 2:
        this.setField2(row.get(2));
        break;

我可以在 for 循环中执行此操作而不是切换大小写吗?我有超过 3 个字段,所以它不是很实用。我听说反射可能是一种选择。我想要类似的东西

for (i = 0; i < headers.length; i++) {
        line.setField[i]=row.get(i);
        }
        list.add(line);
    }

这可能吗?使用 JDK 1.6

【问题讨论】:

  • 为什么不使用ArrayList&lt;Object&gt; 而不是多个单个元素?
  • 因为这是作为 JasperReports 的数据源导出的,因此需要 Bean

标签: java performance for-loop reflection javabeans


【解决方案1】:

你可以,问题是,如果你应该。当然,您可以动态搜索方法并通过反射调用它们。

Class<?> clz = Bean.class;
Method[] methods = clz.getDeclaredMethods();
// etc.
methods[i].invoke( ... );

但这并不能帮助您使您的代码更具可读性——而且它会减慢您的应用程序的速度。此外,您当然会失去很多类型安全和编译器检查,可能会用运行时发生的异常替换许多编译器错误。只有在没有其他选择的情况下才应该这样做。

【讨论】:

  • 我想过,但它需要是一个 Bean,因为这是要导出到 JasperSoft Studio 并且它需要一个 JavaBean 数据源,其中每个 Bean 元素在 jrxml 模板中设置一个字段
  • 随意使用反射,它会工作,没有问题,但我只会在非常通用的代码中这样做。
  • 所以 switch case 比反射更友好?
  • 请注意,Javadoc 关于 getDeclaredMethods() “...返回的数组中的元素未排序且未按任何特定顺序”。在按索引选择之前以某种方式对它们进行排序可能是个好主意。
  • 虽然反射确实比 switch 慢,但您可能不必在这里考虑 - setter 的性能几乎不会减慢您的应用程序的速度。您最应该关注的是可读性——即哪种解决方案更容易理解。
【解决方案2】:

这听起来像是 Java 8 的理想任务:

// keep and reuse this list:
List<BiConsumer<Bean,Object>> setters=Arrays.asList(
  Bean::setField0, Bean::setField1, Bean::setField2);

Bean bean;
List<Object> values;

assert setters.size()==values.size();
for(int i=0, num=setters.size(); i<num; i++)
    setters.get(i).accept(bean, values.get(i));

此代码不使用反射,而是使用已定义属性的显式列表,因此您会在编译时注意到错误。但是添加新属性仍然像在开头的列表中添加Bean::setNewProperty 一样简单……

所有 bean 的构造可能如下所示:

List<List<?>> body;

List<Bean> beans=body.stream().map(row-> {
    Bean bean=new Bean();
    for(int i=0, num=setters.size(); i<num; i++)
        setters.get(i).accept(bean, row.get(i));
    return bean;
}).collect(Collectors.toList());

【讨论】:

  • 不使用反射的缺点是现在您必须记住每次添加新属性时都向该列表添加一个 setter。不好。或者,如果并非所有属性都存在所有设置器,则您编写的单元测试会失败——但是您必须在这样的测试中使用反射
  • @Samuel:在测试用例中使用反射比在生产代码中要好得多。除此之外,反射代码并不神奇。只是在方法名称上犯了错误可能会导致它默默地认为方法不是属性。
  • 如果你更新你的bean,需求显然已经改变了。如果是这样的话,无论如何都应该更新测试用例——最好是先更新。
  • @Holger 就像任何其他类型的代码反射一样并不神奇。它需要像任何其他代码一样正确编写,并且需要像任何代码一样进行测试。我不认为这是不在生产中使用它的理由,尤其是当它避免在添加方法时必须更新某些类时。另外,您将自己与一个特定的 bean 耦合——这确实限制了对象的有用性。授予的 OP 可能不关心重用它。
  • @Samuel:有人必须确保传入的值列表与将要设置的属性匹配。通过反射自动查找新属性并不能解决这个问题。除此之外,正如您自己所说,您必须通过至少删除像getClass() 这样的只读属性来清理自动发现的结果。
【解决方案3】:

Apache commons-beanutils 是一个旨在使处理 bean 比使用原始反射更容易的项目。你可以这样做:

Map<String, String> properties = BeanUtils.describe(bean);
List<String> orderedProperties = new ArrayList<>(properties.keySet()); // cache in an instance variable

// sometime later...
BeanUtils.setProperty(obj, orderedProperties.get(i), value);

请注意 BeanUtils,因为我似乎记得它认为 getClass() 是一个属性,因为它看起来像一个 getter。您可能想要检查 BeanUtils 找到的每个属性的 setter。

【讨论】:

  • 四年前我就应该知道这一点,然后我自己写了一半;-)
猜你喜欢
  • 1970-01-01
  • 2018-05-19
  • 1970-01-01
  • 2013-12-24
  • 1970-01-01
  • 2012-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多