【问题标题】:Java - recursively modify object values with reflectionJava - 使用反射递归修改对象值
【发布时间】:2019-02-25 17:06:54
【问题描述】:

我想转换 Object 的每个 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);

到目前为止,一切正常,如果我执行此操作,那么所有字符串值最后都会附加“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>>

【问题讨论】:

  • 你想为ListMap做什么?你想在Map 中转换keys 吗?
  • Matt Champion 已经就循环的来源提供了一个很好的答案。至于列表和地图处理——您必须为它们添加一个特殊情况并遍历元素而不是遍历这些类的内部字段。请注意,由于类型擦除,您将无法通过反射确定列表的参数化类型——您必须逐个检查所有元素并检查它们的类型。但是您将面临一些更有趣的挑战,例如如何处理您根本无法更改的不可变字符串列表。
  • 我可以添加一个方法来遍历列表的所有元素并修改值(如果它们是字符串)。那行得通。但是,如果我们有一个像 List>> 或 Map> 这样的数据结构,我想修改这些数据结构的所有字符串值怎么办?

标签: java reflection


【解决方案1】:

ArrayList 包含一个 long serialVersionUID 字段以帮助进行序列化。当您获得该值时,它会返回一个装箱的Long。在Long 上调用getDeclaredFields 会返回一个包含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,因为您关心对象的实例而不是它们的相等性。

【讨论】:

  • 对。不抛出 StackOverflow Exception 的问题就这样解决了。但是我还必须以某种方式转换 Map 或 List 的 String 值。
猜你喜欢
  • 2016-10-20
  • 2013-04-23
  • 2015-11-18
  • 2011-09-05
  • 2011-02-07
  • 2012-10-03
  • 1970-01-01
  • 2016-01-24
  • 2011-02-11
相关资源
最近更新 更多