上个项目有个集合拷贝的工具,用过但没细看怎么实现的,今天突然想起来就按自己的思路写一个,写到一半发现网上有更好的办法,直接把集合转 JsonArray ,再转成自己想要的类型的集合即可,写了一两个小时也不想删了,目前支持拷贝自己和父类非 final 修饰的字段,还不支持 List<A> A里还包个B类的情况。
/**
* 反射copy集合 List<A> -> List<B> 字段名,类型相同就copy
* @param resources
* @param clz
* @param <T>
* @return
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IntrospectionException
* @throws InvocationTargetException
*/
private static <T> List<T> copyCollection(List<?> resources, Class<T> clz) throws InstantiationException, IllegalAccessException, IntrospectionException, InvocationTargetException {
assert !CollectionUtils.isEmpty(resources);
List<T> result = new ArrayList<>();
T single;
// 获取源集合泛型的class
Class<?> sourceClass = resources.get(0).getClass();
List<Field> sourceFields = new ArrayList<>();
// 递归获取自己和父类的字段
sourceFields = getSuperField(sourceClass, sourceFields);
// 获取目标类型的字段
List<Field> targetFields = new ArrayList<>();
targetFields = getSuperField(clz, targetFields);
// 转成 key 是字段名, value 是字段类型的 map,方便校验
Map<String, ? extends Class<?>> fieldNameTypeMap = targetFields.stream().collect(Collectors.toMap(Field::getName, Field::getType));
PropertyDescriptor propertyDescriptor;
for (Object resource : resources) {
// 实例化目标类型
single = clz.newInstance();
for (Field declaredField : sourceFields) {
declaredField.setAccessible(true);
String fieldName = declaredField.getName();
// 字段不被 finla 修饰,并且目标类包含此字段,并且字段类型一致才能继续走逻辑
if (Modifier.isFinal(declaredField.getModifiers())
|| !fieldNameTypeMap.containsKey(fieldName)
|| !fieldNameTypeMap.get(fieldName).equals(declaredField.getType())) {
continue;
}
// 获取字段值
Object value = declaredField.get(resource);
// 获取字段的 set 方法,并设置值
propertyDescriptor = new PropertyDescriptor(fieldName, clz);
propertyDescriptor.getWriteMethod().invoke(single, value);
}
result.add(single);
}
return result;
}
/**
* 递归获取自己和父类的全部字段,包括父类的父类
* @param clz
* @param result
* @return
*/
private static List<Field> getSuperField(Class<?> clz, List<Field> result) {
Class<?> superclass = clz.getSuperclass();
result.addAll(Arrays.asList(clz.getDeclaredFields()));
if (ObjectUtils.isEmpty(superclass)) {
return result;
} else {
return getSuperField(superclass, result);
}
}