运用 Java 8 写一个 通用 Map 转换工具类

栏目: 编程语言 · Java · 发布时间: 5年前

内容简介:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangxin09/article/details/86670936

版权声明:本文为博主原创文章,未经博主允许不得转载。 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 泛型转换器

为了转换泛型,如 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 文件解析足够了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

搜索引擎

搜索引擎

李晓明 / 科学出版社发行部 / 2005-4 / 33.00元

《搜索引擎:原理技术与系统》系统地介绍了互联网搜索引擎的工作原理、实现技术及其系统构建方案。《搜索引擎:原理技术与系统》分三篇共13章内容,从基本工作原理概述,到一个小型简单搜索引擎具体细节的实现,进而详细讨论了大规模分布式搜索引擎系统的设计要点及其关键技术;最后介绍了面向主题和个性化的web信息服务,阐述了中文网页自动分类等技术及其应用。《搜索引擎:原理技术与系统》层次分明,由浅入深;既有深入的......一起来看看 《搜索引擎》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具