将一个JavaBean中的非空属性转到Map中

最近在写项目的时候遇到一个这样的一个需要:将JavaBean中非空属性转到一个Map中。stackoverflow出一个还算不错的回答,记录如下:

stackoverflow地址:https://stackoverflow.com/questions/8524011/java-reflection-how-can-i-get-the-all-getter-methods-of-a-java-class-and-invoke

最佳答案(大概意思的翻译):

不要用正则表达式,使用Introspector:

1
2
3
4
5
6
7
for(PropertyDescriptor propertyDescriptor :
Introspector.getBeanInfo(yourClass).getPropertyDescriptors()){
// propertyEditor.getReadMethod() exposes the getter
// btw, this may be null if you have a write-only property
System.out.println(propertyDescriptor.getReadMethod());
}

通常情况下,我们不需要Object.class的属性,所以我们可以使用以下方法:

1
2
3
Introspector.getBeanInfo(yourClass, stopClass)
// usually with Object.class as 2nd param
// the first class is inclusive, the second exclusive

我们还可以使用commons/beanutils的一些方法,如:Map<String, String> properties = BeanUtils.describe(yourObject);它会找到并执行所有的getter并将结果存储到Map中,而且BeanUtils.describe()在返回之前将所有的属性值转换为字符串。

以下是一个用Java 8写的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static Map<String, Object> beanProperties(Object bean) {
try {
return Arrays.asList(
Introspector.getBeanInfo(bean.getClass(), Object.class)
.getPropertyDescriptors()
)
.stream()
// filter out properties with setters only
.filter(pd -> Objects.nonNull(pd.getReadMethod()))
.collect(Collectors.toMap(
// bean property name
PropertyDescriptor::getName,
pd -> { // invoke method to get value
try {
return pd.getReadMethod().invoke(bean);
} catch (Exception e) {
// replace this with better error handling
return null;
}
}));
} catch (IntrospectionException e) {
// and this, too
return Collections.emptyMap();
}
}

以上方法有一个缺点:Collectors.toMap()对于null的值会报错。所以,有了以下版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static Map<String, Object> beanProperties(Object bean) {
try {
Map<String, Object> map = new HashMap<>();
Arrays.asList(Introspector.getBeanInfo(bean.getClass(), Object.class)
.getPropertyDescriptors())
.stream()
// filter out properties with setters only
.filter(pd -> Objects.nonNull(pd.getReadMethod()))
.forEach(pd -> { // invoke method to get value
try {
Object value = pd.getReadMethod().invoke(bean);
if (value != null) {
map.put(pd.getName(), value);
}
} catch (Exception e) {
// add proper error handling here
}
});
return map;
} catch (IntrospectionException e) {
// and here, too
return Collections.emptyMap();
}
}

Guava版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static Map<String, Object> guavaBeanProperties(Object bean) {
Object NULL = new Object();
try {
return Maps.transformValues(
Arrays.stream(
Introspector.getBeanInfo(bean.getClass(), Object.class)
.getPropertyDescriptors())
.filter(pd -> Objects.nonNull(pd.getReadMethod()))
.collect(ImmutableMap::<String, Object>builder,
(builder, pd) -> {
try {
Object result = pd.getReadMethod()
.invoke(bean);
builder.put(pd.getName(),
firstNonNull(result, NULL));
} catch (Exception e) {
throw propagate(e);
}
},
(left, right) -> left.putAll(right.build()))
.build(), v -> v == NULL ? null : v);
} catch (IntrospectionException e) {
throw propagate(e);
}
}

另外,还有一个JavaSlang的版本。基本上很少用这个,就不收集了。