Java-用反射递归修改对象值

时间:2019-02-24 21:22:55

标签: java reflection

我想转换对象的每个String属性(及其嵌套对象),并且我使用以下递归方法通过反射API来实现:

    public static void reflect(Object obj) {
        if (obj == null) {
            return;
        }
        Class klazz = obj.getClass();
        if (klazz.isPrimitive()
                || obj instanceof Integer
                || obj instanceof Double
                || obj instanceof Boolean)
            return;
        else {
            try {
                for (Field field : klazz.getDeclaredFields()) {
                    field.setAccessible(true);
                    Object f = field.get(obj);
                    if(f instanceof String) {
                        f = transform(f);
                        field.set(obj, f);
                    }
                    else {
                        reflect(f);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
    }

    private static Object transform(Object f) {
        f = f + "blabla";
        return f;
    }


@Data
@Builder
public class PrintObject {
    private String field1;
    private String field2;
    private String field3;
    private NestedObject field4;
}

@Data
@Builder
public class NestedObject {
    private String field1;
    private String field2;
    private Integer field3;
}

NestedObject nestedObject = NestedObject
                .builder()
                .field1("test")
                .field2("test2")
                .field3(1)
                .build();

PrintObject printObject = PrintObject
      .builder()
      .field1("test")
      .field2("Test")
      .field3("test")
      .field4(nestedObject)
      .build();

Utils.reflect(printObject);

到目前为止,每个方法都可以正常工作,如果我执行此操作,则所有String值都将最后附加“ blabla”。 如果PrintObject具有其他数据结构(如List或Map),则会出现问题。 例如,如果PrintObject类中还有另一个字段:

private List<String> field5;

然后,此代码执行将引发StackOverflowError。

List<String> list = new ArrayList<>();
list.add("test");

NestedObject nestedObject = NestedObject
                .builder()
                .field1("test")
                .field2("test2")
                .field3(1)
                .build();

PrintObject printObject = PrintObject
      .builder()
      .field1("test")
      .field2("Test")
      .field3("test")
      .field4(nestedObject)
      .field5(list)
      .build();

Utils.reflect(printObject);

关于如何使这些结构也起作用的任何想法? 预先感谢。

field5也可以是例如:

 Map<String,String>

甚至

 List<List<String>>

1 个答案:

答案 0 :(得分:1)

ArrayList包含一个long serialVersionUID字段,以帮助进行序列化。当您获得该值时,它将返回一个带框的Long。在getDeclaredFields上调用Long返回一个包含字段Long.MIN_VALUE的数组,该字段是Long。那就是无限循环的来源。

要解决此问题,我将像为Long一样为Integer添加特殊情况处理。您还应该考虑所有其他框式原语,例如FloatByte

将通过引用彼此链接LinkedList的结构或数组来支持集合。对于链接结构,代码将遍历它们。为了支持收集的数组支持,您需要确定哪些字段是数组并对其进行迭代。

字段的类型,可通过Field.getType获得。数组可以用Class.isArray标识。不同类型的数组具有不同的类型,它们并非像Java泛型一样未经验证。可以将非基本值的数组强制转换为Object[],在这种情况下很有用,但它是not type safe。要获取数组Class.getComponentType中的对象类型,可以使用。

要对数组的条目进行递归,需要执行以下操作。

final Class<?> fieldType = field.getType();
if (fieldType.isArray() && !fieldType.getComponentType().isPrimitive()) {
    Object[] fs = (Object[]) f;
    for (Object fi : fs) {
        reflect(fi);
    }
}

另一个问题是循环引用,它可能进一步导致StackOverflowException。如果将列表作为成员添加到其自身,它将无限递归。有必要跟踪以前访问过的对象,而不要两次访问它们。理想情况下,当您关心对象的实例而不是对象的相等性时,它将使用IdentityHashMap