diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java index c034aa22b..9f05be414 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java @@ -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.impl.RecordConverter; 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.Dict; import org.dromara.hutool.core.map.MapUtil; @@ -297,6 +298,16 @@ public class BeanUtil { // region ----- beanToMap + /** + * 将Bean包装为Map形式 + * @param bean Bean + * @return {@link BeanMap} + * @since 6.0.0 + */ + public static Map toBeanMap(final Object bean) { + return BeanMap.of(bean); + } + /** * 将bean的部分属性转换成map
* 可选拷贝哪些属性值,默认是不忽略值为{@code null}的值的。 diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/ListNode.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/ListNode.java index 2173cc250..99bf241ab 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/ListNode.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/ListNode.java @@ -62,7 +62,8 @@ public class ListNode implements Node{ // 只支持String为key的Map return MapUtil.getAny((Map) bean, unWrappedNames); } else { - final Map map = BeanUtil.beanToMap(bean); + // 一次性使用,包装Bean避免无用转换 + final Map map = BeanUtil.toBeanMap(bean); return MapUtil.getAny(map, unWrappedNames); } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java index 869d2db06..6e78584f0 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java @@ -84,16 +84,17 @@ public class EntryConverter implements MatcherConverter, Serializable { if (value instanceof Map.Entry) { final Map.Entry entry = (Map.Entry) value; map = MapUtil.of(entry.getKey(), entry.getValue()); - }else if (value instanceof Pair) { + } else if (value instanceof Pair) { final Pair entry = (Pair) value; map = MapUtil.of(entry.getLeft(), entry.getRight()); - }else if (value instanceof Map) { + } else if (value instanceof Map) { map = (Map) value; } else if (value instanceof CharSequence) { final CharSequence str = (CharSequence) value; map = strToMap(str); } else if (BeanUtil.isWritableBean(value.getClass())) { - map = BeanUtil.beanToMap(value); + // 一次性只读场景,包装为Map效率更高 + map = BeanUtil.toBeanMap(value); } if (null != map) { @@ -133,13 +134,14 @@ public class EntryConverter implements MatcherConverter, Serializable { @SuppressWarnings("rawtypes") private static Map.Entry mapToEntry(final Type targetType, final Type keyType, final Type valueType, final Map map) { - Object key = null; - Object value = null; + final Object key; + final Object value; if (1 == map.size()) { final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); key = entry.getKey(); value = entry.getValue(); - } else if (2 == map.size()) { + } else { + // 忽略Map中其它属性 key = map.get("key"); value = map.get("value"); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/PairConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/PairConverter.java index 200f261c7..b031607a4 100755 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/PairConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/PairConverter.java @@ -84,7 +84,8 @@ public class PairConverter implements Converter { final CharSequence str = (CharSequence) value; map = strToMap(str); } else if (BeanUtil.isReadableBean(value.getClass())) { - map = BeanUtil.beanToMap(value); + // 一次性只读场景,包装为Map效率更高 + map = BeanUtil.toBeanMap(value); } if (null != map) { @@ -123,13 +124,14 @@ public class PairConverter implements Converter { @SuppressWarnings("rawtypes") private static Pair mapToPair(final Type keyType, final Type valueType, final Map map) { - Object left = null; - Object right = null; + final Object left; + final Object right; if (1 == map.size()) { final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); left = entry.getKey(); right = entry.getValue(); - } else if (2 == map.size()) { + } else { + // 忽略Map中其它属性 left = map.get("left"); right = map.get("right"); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TripleConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TripleConverter.java index 4e212384b..e04ed9061 100755 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TripleConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TripleConverter.java @@ -70,7 +70,8 @@ public class TripleConverter implements Converter { throws ConvertException { Map map = null; if (BeanUtil.isReadableBean(value.getClass())) { - map = BeanUtil.beanToMap(value); + // 一次性只读场景,包装为Map效率更高 + map = BeanUtil.toBeanMap(value); } if (null != map) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/BeanMap.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/BeanMap.java new file mode 100644 index 000000000..603a737ac --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/BeanMap.java @@ -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接口实现
+ * 通过反射方式,将一个Bean的操作转化为Map操作 + * + * @author Looly + * @since 6.0.0 + */ +public class BeanMap implements Map { + + /** + * 构建BeanMap + * + * @param bean Bean + * @return BeanMap + */ + public static BeanMap of(final Object bean) { + return new BeanMap(bean); + } + + private final Object bean; + private final Map 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 m) { + m.forEach(this::put); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Can not clear fields for Bean!"); + } + + @Override + public Set keySet() { + return this.propDescMap.keySet(); + } + + @Override + public Collection values() { + final List list = new ArrayList<>(size()); + for (final PropDesc propDesc : this.propDescMap.values()) { + list.add(propDesc.getValue(bean)); + } + return list; + } + + @Override + public Set> entrySet() { + final HashSet> set = new HashSet<>(size(), 1); + this.propDescMap.forEach((key, propDesc) -> set.add(new AbstractMap.SimpleEntry<>(key, propDesc.getValue(bean)))); + return set; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/Dict.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/Dict.java index 28d73333e..3f1a54a99 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/map/Dict.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/Dict.java @@ -246,7 +246,8 @@ public class Dict extends CustomKeyMap implements TypeGetter Dict parseBean(final T bean) { Assert.notNull(bean, "Bean must not be null"); - this.putAll(BeanUtil.beanToMap(bean)); + // 一次性使用,避免先生成Map,再复制造成空间浪费 + this.putAll(BeanUtil.toBeanMap(bean)); return this; } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java index 4b96ffbf3..8b706ef2c 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java @@ -209,6 +209,23 @@ public class BeanUtilTest { 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 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 public void beanToMapNullPropertiesTest() { final SubPerson person = new SubPerson();