add BeanMap

This commit is contained in:
Looly 2024-08-25 20:28:28 +08:00
parent 3a73d3d0a0
commit e67f37fc2f
8 changed files with 185 additions and 13 deletions

View File

@ -25,6 +25,7 @@ import org.dromara.hutool.core.collection.set.SetUtil;
import org.dromara.hutool.core.convert.ConvertUtil; import org.dromara.hutool.core.convert.ConvertUtil;
import org.dromara.hutool.core.convert.impl.RecordConverter; import org.dromara.hutool.core.convert.impl.RecordConverter;
import org.dromara.hutool.core.lang.mutable.MutableEntry; import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.core.map.BeanMap;
import org.dromara.hutool.core.map.CaseInsensitiveMap; import org.dromara.hutool.core.map.CaseInsensitiveMap;
import org.dromara.hutool.core.map.Dict; import org.dromara.hutool.core.map.Dict;
import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.map.MapUtil;
@ -297,6 +298,16 @@ public class BeanUtil {
// region ----- beanToMap // region ----- beanToMap
/**
* 将Bean包装为Map形式
* @param bean Bean
* @return {@link BeanMap}
* @since 6.0.0
*/
public static Map<String, Object> toBeanMap(final Object bean) {
return BeanMap.of(bean);
}
/** /**
* 将bean的部分属性转换成map<br> * 将bean的部分属性转换成map<br>
* 可选拷贝哪些属性值默认是不忽略值为{@code null}的值的 * 可选拷贝哪些属性值默认是不忽略值为{@code null}的值的

View File

@ -62,7 +62,8 @@ public class ListNode implements Node{
// 只支持String为key的Map // 只支持String为key的Map
return MapUtil.getAny((Map<String, ?>) bean, unWrappedNames); return MapUtil.getAny((Map<String, ?>) bean, unWrappedNames);
} else { } else {
final Map<String, Object> map = BeanUtil.beanToMap(bean); // 一次性使用包装Bean避免无用转换
final Map<String, Object> map = BeanUtil.toBeanMap(bean);
return MapUtil.getAny(map, unWrappedNames); return MapUtil.getAny(map, unWrappedNames);
} }
} }

View File

@ -84,16 +84,17 @@ public class EntryConverter implements MatcherConverter, Serializable {
if (value instanceof Map.Entry) { if (value instanceof Map.Entry) {
final Map.Entry entry = (Map.Entry) value; final Map.Entry entry = (Map.Entry) value;
map = MapUtil.of(entry.getKey(), entry.getValue()); map = MapUtil.of(entry.getKey(), entry.getValue());
}else if (value instanceof Pair) { } else if (value instanceof Pair) {
final Pair entry = (Pair<?, ?>) value; final Pair entry = (Pair<?, ?>) value;
map = MapUtil.of(entry.getLeft(), entry.getRight()); map = MapUtil.of(entry.getLeft(), entry.getRight());
}else if (value instanceof Map) { } else if (value instanceof Map) {
map = (Map) value; map = (Map) value;
} else if (value instanceof CharSequence) { } else if (value instanceof CharSequence) {
final CharSequence str = (CharSequence) value; final CharSequence str = (CharSequence) value;
map = strToMap(str); map = strToMap(str);
} else if (BeanUtil.isWritableBean(value.getClass())) { } else if (BeanUtil.isWritableBean(value.getClass())) {
map = BeanUtil.beanToMap(value); // 一次性只读场景包装为Map效率更高
map = BeanUtil.toBeanMap(value);
} }
if (null != map) { if (null != map) {
@ -133,13 +134,14 @@ public class EntryConverter implements MatcherConverter, Serializable {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private static Map.Entry<?, ?> mapToEntry(final Type targetType, final Type keyType, final Type valueType, final Map map) { private static Map.Entry<?, ?> mapToEntry(final Type targetType, final Type keyType, final Type valueType, final Map map) {
Object key = null; final Object key;
Object value = null; final Object value;
if (1 == map.size()) { if (1 == map.size()) {
final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
key = entry.getKey(); key = entry.getKey();
value = entry.getValue(); value = entry.getValue();
} else if (2 == map.size()) { } else {
// 忽略Map中其它属性
key = map.get("key"); key = map.get("key");
value = map.get("value"); value = map.get("value");
} }

View File

@ -84,7 +84,8 @@ public class PairConverter implements Converter {
final CharSequence str = (CharSequence) value; final CharSequence str = (CharSequence) value;
map = strToMap(str); map = strToMap(str);
} else if (BeanUtil.isReadableBean(value.getClass())) { } else if (BeanUtil.isReadableBean(value.getClass())) {
map = BeanUtil.beanToMap(value); // 一次性只读场景包装为Map效率更高
map = BeanUtil.toBeanMap(value);
} }
if (null != map) { if (null != map) {
@ -123,13 +124,14 @@ public class PairConverter implements Converter {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private static Pair<?, ?> mapToPair(final Type keyType, final Type valueType, final Map map) { private static Pair<?, ?> mapToPair(final Type keyType, final Type valueType, final Map map) {
Object left = null; final Object left;
Object right = null; final Object right;
if (1 == map.size()) { if (1 == map.size()) {
final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
left = entry.getKey(); left = entry.getKey();
right = entry.getValue(); right = entry.getValue();
} else if (2 == map.size()) { } else {
// 忽略Map中其它属性
left = map.get("left"); left = map.get("left");
right = map.get("right"); right = map.get("right");
} }

View File

@ -70,7 +70,8 @@ public class TripleConverter implements Converter {
throws ConvertException { throws ConvertException {
Map map = null; Map map = null;
if (BeanUtil.isReadableBean(value.getClass())) { if (BeanUtil.isReadableBean(value.getClass())) {
map = BeanUtil.beanToMap(value); // 一次性只读场景包装为Map效率更高
map = BeanUtil.toBeanMap(value);
} }
if (null != map) { if (null != map) {

View File

@ -0,0 +1,137 @@
/*
* Copyright (c) 2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.core.map;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.bean.PropDesc;
import org.dromara.hutool.core.util.ObjUtil;
import java.util.*;
/**
* Bean的Map接口实现<br>
* 通过反射方式将一个Bean的操作转化为Map操作
*
* @author Looly
* @since 6.0.0
*/
public class BeanMap implements Map<String, Object> {
/**
* 构建BeanMap
*
* @param bean Bean
* @return BeanMap
*/
public static BeanMap of(final Object bean) {
return new BeanMap(bean);
}
private final Object bean;
private final Map<String, PropDesc> propDescMap;
/**
* 构造
*
* @param bean Bean
*/
public BeanMap(final Object bean) {
this.bean = bean;
this.propDescMap = BeanUtil.getBeanDesc(bean.getClass()).getPropMap(false);
}
@Override
public int size() {
return this.propDescMap.size();
}
@Override
public boolean isEmpty() {
return propDescMap.isEmpty();
}
@Override
public boolean containsKey(final Object key) {
return this.propDescMap.containsKey(key);
}
@Override
public boolean containsValue(final Object value) {
for (final PropDesc propDesc : this.propDescMap.values()) {
if (ObjUtil.equals(propDesc.getValue(bean), value)) {
return true;
}
}
return false;
}
@Override
public Object get(final Object key) {
final PropDesc propDesc = this.propDescMap.get(key);
if (null != propDesc) {
return propDesc.getValue(bean);
}
return null;
}
@Override
public Object put(final String key, final Object value) {
final PropDesc propDesc = this.propDescMap.get(key);
if (null != propDesc) {
final Object oldValue = propDesc.getValue(bean);
propDesc.setValue(bean, value);
return oldValue;
}
return null;
}
@Override
public Object remove(final Object key) {
throw new UnsupportedOperationException("Can not remove field for Bean!");
}
@Override
public void putAll(final Map<? extends String, ?> m) {
m.forEach(this::put);
}
@Override
public void clear() {
throw new UnsupportedOperationException("Can not clear fields for Bean!");
}
@Override
public Set<String> keySet() {
return this.propDescMap.keySet();
}
@Override
public Collection<Object> values() {
final List<Object> list = new ArrayList<>(size());
for (final PropDesc propDesc : this.propDescMap.values()) {
list.add(propDesc.getValue(bean));
}
return list;
}
@Override
public Set<Entry<String, Object>> entrySet() {
final HashSet<Entry<String, Object>> set = new HashSet<>(size(), 1);
this.propDescMap.forEach((key, propDesc) -> set.add(new AbstractMap.SimpleEntry<>(key, propDesc.getValue(bean))));
return set;
}
}

View File

@ -246,7 +246,8 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
*/ */
public <T> Dict parseBean(final T bean) { public <T> Dict parseBean(final T bean) {
Assert.notNull(bean, "Bean must not be null"); Assert.notNull(bean, "Bean must not be null");
this.putAll(BeanUtil.beanToMap(bean)); // 一次性使用避免先生成Map再复制造成空间浪费
this.putAll(BeanUtil.toBeanMap(bean));
return this; return this;
} }

View File

@ -209,6 +209,23 @@ public class BeanUtilTest {
assertFalse(map.containsKey("SUBNAME")); assertFalse(map.containsKey("SUBNAME"));
} }
@Test
public void toBeanMapTest() {
final SubPerson person = new SubPerson();
person.setAge(14);
person.setOpenid("11213232");
person.setName("测试A11");
person.setSubName("sub名字");
final Map<String, Object> map = BeanUtil.toBeanMap(person);
assertEquals("测试A11", map.get("name"));
assertEquals(14, map.get("age"));
assertEquals("11213232", map.get("openid"));
// static属性应被忽略
assertFalse(map.containsKey("SUBNAME"));
}
@Test @Test
public void beanToMapNullPropertiesTest() { public void beanToMapNullPropertiesTest() {
final SubPerson person = new SubPerson(); final SubPerson person = new SubPerson();