版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangxin09/article/details/86670936
Map 是非常常见的一个数据结构,至于多常见则不再赘说了。框架无论大小,都会多少提供 Map 的相关工具方法,或进行封装。笔者在没用使用 Java 8 之前,也封装过,用了一段时间,如今 Java 8 问世几年,是时候对库改造一番了。
库源码: https://gitee.com/sp42_admin/ajaxjs/blob/master/ajaxjs-base/src/main/java/com/ajaxjs/util/MapTool.java
我们知道,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()))); }
反之,将字符串转换为 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<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")); }
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 部分的代码 copy 第三方代码,没什么好说了。我一向主张使用自带的库,使用直接使用了 Java W3C Dom 解析,小型 XML 文件解析足够了。