Map 是非常常见的一个数据结构,至于多常见则不再赘说了。框架无论大小,都会多少提供 Map 的相关工具方法,或进行封装。笔者在没用使用 Java 8 之前,也封装过,用了一段时间,如今 Java 8 问世几年,是时候对库改造一番了。
join()
我们知道,String [] 有 join 的方法,把多个 String 转换为字符串,各个元素用 & 联结(或自定义字符),同样我们把该方法延伸到 Map 身上,于是有 join 的方法,
/**
* Map 转换为 String
*
* @param map Map 结构,Key 必须为 String 类型
* @param div 分隔符
* @param fn 对 Value 的处理函数,返回类型 T
* @return Map 序列化字符串
*/
public static <T> String join(Map<String, T> map, String div, Function<T, String> fn) {
String[] pairs = new String[map.size()];
int i = 0;
for (String key : map.keySet())
pairs[i++] = key + "=" + fn.apply(map.get(key));
return String.join(div, pairs);
}
测试:
Map<String, Object> map = new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("foo", null);
put("bar", 500);
put("zx", "hi");
}
};
@Test
public void testJoin() {
assertEquals("bar=500&foo=null&zx=hi", join(as(map, v -> v.toString())));
}
toMap
反之,将字符串转换为 Map,则有 toMap 方法,分别有以下两种情形:
/**
* String[] 转换为 Map
*
* @param pairs 结对的字符串数组,包含 = 字符分隔 key 和 value
* @param fn 对 Value 的处理函数,返回类型 Object
* @return Map 对象
*/
public static Map<String, Object> toMap(String[] pairs, Function<String, Object> fn) {
if (CommonUtil.isNull(pairs))
return null;
Map<String, Object> map = new HashMap<>();
for (String pair : pairs) {
if (!pair.contains("="))
throw new IllegalArgumentException("没有 = 不能转化为 map");
String[] column = pair.split("=");
if (column.length >= 2)
map.put(column[0], fn == null ? column[1] : fn.apply(column[1]));
else
map.put(column[0], "");// 没有 等号后面的,那设为空字符串
}
return map;
}
/**
* String[] 转换为 Map,key 与 value 分别一个数组
*
* @param columns 结对的键数组
* @param values 结对的值数组
* @param fn 对 Value 的处理函数,返回类型 Object
* @return Map 对象
*/
public static Map<String, Object> toMap(String[] columns, String[] values, Function<String, Object> fn) {
if (CommonUtil.isNull(columns))
return null;
if (columns.length != values.length)
throw new UnsupportedOperationException("两个数组 size 不一样");
Map<String, Object> map = new HashMap<>();
int i = 0;
for (String column : columns)
map.put(column, fn.apply(values[i++]));
return map;
}
测试:
@Test
public void testToMap() {
assertEquals(1, MapTool.toMap(new String[] { "a", "b", "d" }, new String[] { "1", "c", "2" }, MappingValue::toJavaValue).get("a"));
assertEquals(1, MapTool.toMap(new String[] { "a=1", "b=2", "d=c" }, MappingValue::toJavaValue).get("a"));
assertEquals("你好", MapTool.toMap(new String[] { "a=%e4%bd%a0%e5%a5%bd", "b=2", "d=c" }, Encode::urlDecode).get("a"));
}
值得一提的是,MappingValue::toJavaValue 能把字符串还原为 Java 里面的真实值,如 “true”–true,“123”–123,“null”–null,源码如下,
/**
* 把字符串还原为 Java 里面的真实值,如 "true"--true,"123"--123,"null"--null
*
* @param value 字符串的值
* @return Java 里面的值
*/
public static Object toJavaValue(String value) {
if (value == null)
return null;
value = value.trim();
if ("".equals(value))
return "";
if ("null".equals(value))
return null;
if ("true".equalsIgnoreCase(value))
return true;
if ("false".equalsIgnoreCase(value))
return false;
// try 比较耗资源,先检查一下
if (value.charAt(0) == '-' || (value.charAt(0) >= '0' && value.charAt(0) <= '9'))
try {
int int_value = Integer.parseInt(value);
if ((int_value + "").equals(value)) // 判断为整形
return int_value;
} catch (NumberFormatException e) {// 不能转换为数字
}
return value;
}
代码比较简单,主要是结合了 Java 8 特性发挥,理解函数可以作为变量“传来传去”就好了。
万能 Map 泛型转换器
为了转换泛型,如 Map<String, Object>
与 Map<String, String>
之间的互转,提供了该方法——说“万能”的口气好像比较大,但扒开源码呢,还是没啥技术含量的,顶多使用了 Function<K, T> fn
函数接口。源码如下,
/**
* 万能 Map 转换器,为了泛型的转换而设的一个方法,怎么转换在 fn 中处理
*
* @param map 原始 Map,key 必须为 String 类型
* @param fn 转换函数
* @return
*/
public static <T, K> Map<String, T> as(Map<String, K> map, Function<K, T> fn) {
Map<String, T> _map = new HashMap<>();
for (String key : map.keySet()) {
K value = map.get(key);
_map.put(key.toString(), value == null ? null : fn.apply(value));
}
return _map;
}
需要注意的是 key 必须为 String 类型。如果不限制,应该也是可以,代码就要复杂一点,当前先不考虑复杂的情况。
测试:
@Test
public void testToMap() {
assertEquals(1, MapTool.toMap(new String[] { "a", "b", "d" }, new String[] { "1", "c", "2" }, MappingValue::toJavaValue).get("a"));
assertEquals(1, MapTool.toMap(new String[] { "a=1", "b=2", "d=c" }, MappingValue::toJavaValue).get("a"));
assertEquals("你好", MapTool.toMap(new String[] { "a=%e4%bd%a0%e5%a5%bd", "b=2", "d=c" }, Encode::urlDecode).get("a"));
}
@Test
public void testAsString() {
assertEquals("500", as(map, v -> v.toString()).get("bar"));
assertEquals("[1, c, 2]", as(new HashMap<String, String[]>() {
private static final long serialVersionUID = 1L;
{
put("foo", new String[] { "a", "b" });
put("bar", new String[] { "1", "c", "2" });
}
}, v -> Arrays.toString(v)).get("bar"));
}
Map 与 Bean 的转换
Java Bean 又称 POJO,可以没有任何集成,所以根类是 Object。JDK 自带 Bean “内省”,那样就无须经过反射了。我们先把遍历 bean 各个字段的逻辑抽出来,
@FunctionalInterface
public static interface EachFieldArg {
public void item(String key, Object value, PropertyDescriptor property);
}
/**
* 遍历一个 Java Bean
*
* @param bean Java Bean
* @param fn 执行的任务,参数有 key, value, property
*/
public static void eachField(Object bean, EachFieldArg fn) {
try {
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
String key = property.getName();
// 得到 property 对应的 getter 方法
Method getter = property.getReadMethod();
Object value = getter.invoke(bean);
fn.item(key, value, property);
}
} catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOGGER.warning(e);
}
}
遍历本身足够简单,唯一亮点是自定义函数接口的使用:@FunctionalInterface。当 JDK 自带的 Supply、Function、Consumer 参数不能满足需求时,自定义函数接口就发挥作用了,例如 public void item(String key, Object value, PropertyDescriptor property); 我们一下子安排了三个参数。
接下来的事情就好办,无法获取值,设置值,交换数据,还有一些细节问题处理就是了。
/**
* Map 转为 Bean
*
* @param map 原始数据
* @param clz 实体 bean 的类
* @param isTransform 是否尝试转换值
* @return 实体 bean 对象
*/
public static <T> T map2Bean(Map<String, ?> map, Class<T> clz, boolean isTransform) {
T bean = ReflectUtil.newInstance(clz);
eachField(bean, (key, v, property) -> {
try {
if (map.containsKey(key)) {
Object value = map.get(key);
// null 是不会传入 bean 的
if (value != null) {
Class<?> t = property.getPropertyType(); // Bean 值的类型,这是期望传入的类型,也就 setter 参数的类型
if (isTransform && value != null && t != value.getClass()) { // 类型相同,直接传入;类型不相同,开始转换
value = MappingValue.objectCast(value, t);
}
property.getWriteMethod().invoke(bean, value);
}
}
// 子对象
for (String mKey : map.keySet()) {
if (mKey.contains(key + '_')) {
Method getter = property.getReadMethod(), setter = property.getWriteMethod();// 得到对应的 setter 方法
Object subBean = getter.invoke(bean);
String subBeanKey = mKey.replaceAll(key + '_', "");
if (subBean != null) {// 已有子 bean
if (map.get(mKey) != null) // null 值不用处理
ReflectUtil.setProperty(subBean, subBeanKey, map.get(mKey));
} else { // map2bean
Map<String, Object> subMap = new HashMap<>();
subMap.put(subBeanKey, map.get(mKey));
subBean = map2Bean(subMap, setter.getParameterTypes()[0], isTransform);
setter.invoke(bean, subBean); // 保存新建的 bean
}
}
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOGGER.warning(e);
}
});
return bean;
}
/**
* map 转实体
*
* @param map 原始数据
* @param clz 实体 bean 的类
* @return 实体 bean 对象
*/
public static <T> T map2Bean(Map<String, ?> map, Class<T> clz) {
return map2Bean(map, clz, false);
}
/**
* Bean 转为 Map
*
* @param bean 实体 bean 对象
* @return Map 对象
*/
public static <T> Map<String, Object> bean2Map(T bean) {
Map<String, Object> map = new HashMap<>();
eachField(bean, (k, v, property) -> {
if (!k.equals("class")) // 过滤 class 属性
map.put(k, v);
});
return map;
}
测试:
public static Map<String, Object> userWithoutChild = new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("id", 1L);
put("name", "Jack");
put("age", 30);
put("birthday", new Date());
}
};
public static class MapMock {
static boolean s = true;
public static Map<String, Object> user = new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("id", 1L);
put("name", "Jack");
put("sex", s);
put("age", 30);
put("birthday", new Date());
put("children", "Tom,Peter");
put("luckyNumbers", "2, 8, 6");
}
};
}
@Test
public void testMap2Bean() {
TestCaseUserBean user = MapTool.map2Bean(userWithoutChild, TestCaseUserBean.class);// 直接转
assertNotNull(user);
assertEquals(user.getName(), "Jack");
user = MapTool.map2Bean(MapMock.user, TestCaseUserBean.class, true);
assertNotNull(user);
assertEquals("Tom", user.getChildren()[0]);
assertEquals(8, user.getLuckyNumbers()[1]);
assertEquals(true, user.isSex());
}
@Test
public void testBean2Map() {
TestCaseUserBean user = MapTool.map2Bean(MapMock.user, TestCaseUserBean.class, true);
Map<String, Object> map = MapTool.bean2Map(user);
assertNotNull(map);
assertEquals("Jack", map.get("name"));
}
XML 与 Bean 互转
XML 部分的代码 copy 第三方代码,没什么好说了。我一向主张使用自带的库,使用直接使用了 Java W3C Dom 解析,小型 XML 文件解析足够了。