Merge remote-tracking branch 'origin/v5-dev' into v5-dev

# Conflicts:
#	hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java
This commit is contained in:
jiazhengquan 2022-03-09 11:30:15 +08:00
commit 88a9fc4a37
39 changed files with 1696 additions and 330 deletions

View File

@ -2,14 +2,20 @@
# 🚀Changelog # 🚀Changelog
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.23 (2022-03-04) # 5.7.23 (2022-03-09)
### 🐣新特性 ### 🐣新特性
* 【http 】 HttpRequest.form采用TableMap方式issue#I4W427@Gitee * 【http 】 HttpRequest.form采用TableMap方式issue#I4W427@Gitee
* 【core 】 AnnotationUtil增加getAnnotationAlias方法pr#554@Gitee * 【core 】 AnnotationUtil增加getAnnotationAlias方法pr#554@Gitee
* 【core 】 FileUtil.extName增加对tar.gz特殊处理issue#I4W5FS@Gitee * 【core 】 FileUtil.extName增加对tar.gz特殊处理issue#I4W5FS@Gitee
* 【crypto 】 增加XXTEA实现issue#I4WH2X@Gitee
* 【core 】 增加Table实现issue#2179@Github
* 【core 】 增加UniqueKeySetissue#I4WUWR@Gitee
*
### 🐞Bug修复 ### 🐞Bug修复
* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题pr#555@Gitee * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题pr#555@Gitee
* 【core 】 修复NumberConverter对数字转换的问题issue#I4WPF4@Gitee
* 【core 】 修复ReflectUtil.getMethods获取接口方法问题issue#I4WUWR@Gitee
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.7.22 (2022-03-01) # 5.7.22 (2022-03-01)

View File

@ -39,7 +39,7 @@ public class AnnotationProxy<T extends Annotation> implements Annotation, Invoca
@Override @Override
public Class<? extends Annotation> annotationType() { public Class<? extends Annotation> annotationType() {
return null; return type;
} }
@Override @Override

View File

@ -225,8 +225,8 @@ public class BeanUtil {
*/ */
private static Map<String, PropertyDescriptor> internalGetPropertyDescriptorMap(Class<?> clazz, boolean ignoreCase) throws BeanException { private static Map<String, PropertyDescriptor> internalGetPropertyDescriptorMap(Class<?> clazz, boolean ignoreCase) throws BeanException {
final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz); final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz);
final Map<String, PropertyDescriptor> map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1) final Map<String, PropertyDescriptor> map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1f)
: new HashMap<>((int) (propertyDescriptors.length), 1); : new HashMap<>(propertyDescriptors.length, 1);
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
map.put(propertyDescriptor.getName(), propertyDescriptor); map.put(propertyDescriptor.getName(), propertyDescriptor);

View File

@ -70,7 +70,7 @@ public class EqualsBuilder implements Builder<Boolean> {
* Converters value pair into a register pair. * Converters value pair into a register pair.
* </p> * </p>
* *
* @param lhs <code>this</code> object * @param lhs {@code this} object
* @param rhs the other object * @param rhs the other object
* @return the pair * @return the pair
*/ */
@ -82,15 +82,15 @@ public class EqualsBuilder implements Builder<Boolean> {
/** /**
* <p> * <p>
* Returns <code>true</code> if the registry contains the given object pair. * Returns {@code true} if the registry contains the given object pair.
* Used by the reflection methods to avoid infinite loops. * Used by the reflection methods to avoid infinite loops.
* Objects might be swapped therefore a check is needed if the object pair * Objects might be swapped therefore a check is needed if the object pair
* is registered in given or swapped order. * is registered in given or swapped order.
* </p> * </p>
* *
* @param lhs <code>this</code> object to lookup in registry * @param lhs {@code this} object to lookup in registry
* @param rhs the other object to lookup on registry * @param rhs the other object to lookup on registry
* @return boolean <code>true</code> if the registry contains the given object. * @return boolean {@code true} if the registry contains the given object.
* @since 3.0 * @since 3.0
*/ */
static boolean isRegistered(final Object lhs, final Object rhs) { static boolean isRegistered(final Object lhs, final Object rhs) {
@ -108,7 +108,7 @@ public class EqualsBuilder implements Builder<Boolean> {
* Used by the reflection methods to avoid infinite loops. * Used by the reflection methods to avoid infinite loops.
* </p> * </p>
* *
* @param lhs <code>this</code> object to register * @param lhs {@code this} object to register
* @param rhs the other object to register * @param rhs the other object to register
*/ */
static void register(final Object lhs, final Object rhs) { static void register(final Object lhs, final Object rhs) {
@ -131,7 +131,7 @@ public class EqualsBuilder implements Builder<Boolean> {
* <p> * <p>
* Used by the reflection methods to avoid infinite loops. * Used by the reflection methods to avoid infinite loops.
* *
* @param lhs <code>this</code> object to unregister * @param lhs {@code this} object to unregister
* @param rhs the other object to unregister * @param rhs the other object to unregister
* @since 3.0 * @since 3.0
*/ */
@ -170,7 +170,7 @@ public class EqualsBuilder implements Builder<Boolean> {
* @param lhs 此对象 * @param lhs 此对象
* @param rhs 另一个对象 * @param rhs 另一个对象
* @param excludeFields 排除的字段集合如果有不参与计算equals的字段加入此集合即可 * @param excludeFields 排除的字段集合如果有不参与计算equals的字段加入此集合即可
* @return 两个对象是否equals是返回<code>true</code> * @return 两个对象是否equals是返回{@code true}
*/ */
public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection<String> excludeFields) { public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection<String> excludeFields) {
return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class));
@ -182,62 +182,62 @@ public class EqualsBuilder implements Builder<Boolean> {
* @param lhs 此对象 * @param lhs 此对象
* @param rhs 另一个对象 * @param rhs 另一个对象
* @param excludeFields 排除的字段集合如果有不参与计算equals的字段加入此集合即可 * @param excludeFields 排除的字段集合如果有不参与计算equals的字段加入此集合即可
* @return 两个对象是否equals是返回<code>true</code> * @return 两个对象是否equals是返回{@code true}
*/ */
public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) {
return reflectionEquals(lhs, rhs, false, null, excludeFields); return reflectionEquals(lhs, rhs, false, null, excludeFields);
} }
/** /**
* <p>This method uses reflection to determine if the two <code>Object</code>s * <p>This method uses reflection to determine if the two {@code Object}s
* are equal.</p> * are equal.</p>
* *
* <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private * <p>It uses {@code AccessibleObject.setAccessible} to gain access to private
* fields. This means that it will throw a security exception if run under * fields. This means that it will throw a security exception if run under
* a security manager, if the permissions are not set up correctly. It is also * a security manager, if the permissions are not set up correctly. It is also
* not as efficient as testing explicitly. Non-primitive fields are compared using * not as efficient as testing explicitly. Non-primitive fields are compared using
* <code>equals()</code>.</p> * {@code equals()}.</p>
* *
* <p>If the TestTransients parameter is set to <code>true</code>, transient * <p>If the TestTransients parameter is set to {@code true}, transient
* members will be tested, otherwise they are ignored, as they are likely * members will be tested, otherwise they are ignored, as they are likely
* derived fields, and not part of the value of the <code>Object</code>.</p> * derived fields, and not part of the value of the {@code Object}.</p>
* *
* <p>Static fields will not be tested. Superclass fields will be included.</p> * <p>Static fields will not be tested. Superclass fields will be included.</p>
* *
* @param lhs <code>this</code> object * @param lhs {@code this} object
* @param rhs the other object * @param rhs the other object
* @param testTransients whether to include transient fields * @param testTransients whether to include transient fields
* @return <code>true</code> if the two Objects have tested equals. * @return {@code true} if the two Objects have tested equals.
*/ */
public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) {
return reflectionEquals(lhs, rhs, testTransients, null); return reflectionEquals(lhs, rhs, testTransients, null);
} }
/** /**
* <p>This method uses reflection to determine if the two <code>Object</code>s * <p>This method uses reflection to determine if the two {@code Object}s
* are equal.</p> * are equal.</p>
* *
* <p>It uses <code>AccessibleObject.setAccessible</code> to gain access to private * <p>It uses {@code AccessibleObject.setAccessible} to gain access to private
* fields. This means that it will throw a security exception if run under * fields. This means that it will throw a security exception if run under
* a security manager, if the permissions are not set up correctly. It is also * a security manager, if the permissions are not set up correctly. It is also
* not as efficient as testing explicitly. Non-primitive fields are compared using * not as efficient as testing explicitly. Non-primitive fields are compared using
* <code>equals()</code>.</p> * {@code equals()}.</p>
* *
* <p>If the testTransients parameter is set to <code>true</code>, transient * <p>If the testTransients parameter is set to {@code true}, transient
* members will be tested, otherwise they are ignored, as they are likely * members will be tested, otherwise they are ignored, as they are likely
* derived fields, and not part of the value of the <code>Object</code>.</p> * derived fields, and not part of the value of the {@code Object}.</p>
* *
* <p>Static fields will not be included. Superclass fields will be appended * <p>Static fields will not be included. Superclass fields will be appended
* up to and including the specified superclass. A null superclass is treated * up to and including the specified superclass. A null superclass is treated
* as java.lang.Object.</p> * as java.lang.Object.</p>
* *
* @param lhs <code>this</code> object * @param lhs {@code this} object
* @param rhs the other object * @param rhs the other object
* @param testTransients whether to include transient fields * @param testTransients whether to include transient fields
* @param reflectUpToClass the superclass to reflect up to (inclusive), * @param reflectUpToClass the superclass to reflect up to (inclusive),
* may be <code>null</code> * may be {@code null}
* @param excludeFields array of field names to exclude from testing * @param excludeFields array of field names to exclude from testing
* @return <code>true</code> if the two Objects have tested equals. * @return {@code true} if the two Objects have tested equals.
* @since 2.0 * @since 2.0
*/ */
public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class<?> reflectUpToClass, public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class<?> reflectUpToClass,
@ -343,9 +343,9 @@ public class EqualsBuilder implements Builder<Boolean> {
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
/** /**
* <p>Adds the result of <code>super.equals()</code> to this builder.</p> * <p>Adds the result of {@code super.equals()} to this builder.</p>
* *
* @param superEquals the result of calling <code>super.equals()</code> * @param superEquals the result of calling {@code super.equals()}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
* @since 2.0 * @since 2.0
*/ */
@ -360,8 +360,8 @@ public class EqualsBuilder implements Builder<Boolean> {
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
/** /**
* <p>Test if two <code>Object</code>s are equal using their * <p>Test if two {@code Object}s are equal using their
* <code>equals</code> method.</p> * {@code equals} method.</p>
* *
* @param lhs the left hand object * @param lhs the left hand object
* @param rhs the right hand object * @param rhs the right hand object
@ -388,11 +388,11 @@ public class EqualsBuilder implements Builder<Boolean> {
/** /**
* <p> * <p>
* Test if two <code>long</code> s are equal. * Test if two {@code long} s are equal.
* </p> * </p>
* *
* @param lhs the left hand <code>long</code> * @param lhs the left hand {@code long}
* @param rhs the right hand <code>long</code> * @param rhs the right hand {@code long}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
*/ */
public EqualsBuilder append(final long lhs, final long rhs) { public EqualsBuilder append(final long lhs, final long rhs) {
@ -404,10 +404,10 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Test if two <code>int</code>s are equal.</p> * <p>Test if two {@code int}s are equal.</p>
* *
* @param lhs the left hand <code>int</code> * @param lhs the left hand {@code int}
* @param rhs the right hand <code>int</code> * @param rhs the right hand {@code int}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
*/ */
public EqualsBuilder append(final int lhs, final int rhs) { public EqualsBuilder append(final int lhs, final int rhs) {
@ -419,10 +419,10 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Test if two <code>short</code>s are equal.</p> * <p>Test if two {@code short}s are equal.</p>
* *
* @param lhs the left hand <code>short</code> * @param lhs the left hand {@code short}
* @param rhs the right hand <code>short</code> * @param rhs the right hand {@code short}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
*/ */
public EqualsBuilder append(final short lhs, final short rhs) { public EqualsBuilder append(final short lhs, final short rhs) {
@ -434,10 +434,10 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Test if two <code>char</code>s are equal.</p> * <p>Test if two {@code char}s are equal.</p>
* *
* @param lhs the left hand <code>char</code> * @param lhs the left hand {@code char}
* @param rhs the right hand <code>char</code> * @param rhs the right hand {@code char}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
*/ */
public EqualsBuilder append(final char lhs, final char rhs) { public EqualsBuilder append(final char lhs, final char rhs) {
@ -449,10 +449,10 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Test if two <code>byte</code>s are equal.</p> * <p>Test if two {@code byte}s are equal.</p>
* *
* @param lhs the left hand <code>byte</code> * @param lhs the left hand {@code byte}
* @param rhs the right hand <code>byte</code> * @param rhs the right hand {@code byte}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
*/ */
public EqualsBuilder append(final byte lhs, final byte rhs) { public EqualsBuilder append(final byte lhs, final byte rhs) {
@ -464,16 +464,16 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Test if two <code>double</code>s are equal by testing that the * <p>Test if two {@code double}s are equal by testing that the
* pattern of bits returned by <code>doubleToLong</code> are equal.</p> * pattern of bits returned by {@code doubleToLong} are equal.</p>
* *
* <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p> * <p>This handles NaNs, Infinities, and {@code -0.0}.</p>
* *
* <p>It is compatible with the hash code generated by * <p>It is compatible with the hash code generated by
* <code>HashCodeBuilder</code>.</p> * {@code HashCodeBuilder}.</p>
* *
* @param lhs the left hand <code>double</code> * @param lhs the left hand {@code double}
* @param rhs the right hand <code>double</code> * @param rhs the right hand {@code double}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
*/ */
public EqualsBuilder append(final double lhs, final double rhs) { public EqualsBuilder append(final double lhs, final double rhs) {
@ -484,16 +484,16 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Test if two <code>float</code>s are equal byt testing that the * <p>Test if two {@code float}s are equal byt testing that the
* pattern of bits returned by doubleToLong are equal.</p> * pattern of bits returned by doubleToLong are equal.</p>
* *
* <p>This handles NaNs, Infinities, and <code>-0.0</code>.</p> * <p>This handles NaNs, Infinities, and {@code -0.0}.</p>
* *
* <p>It is compatible with the hash code generated by * <p>It is compatible with the hash code generated by
* <code>HashCodeBuilder</code>.</p> * {@code HashCodeBuilder}.</p>
* *
* @param lhs the left hand <code>float</code> * @param lhs the left hand {@code float}
* @param rhs the right hand <code>float</code> * @param rhs the right hand {@code float}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
*/ */
public EqualsBuilder append(final float lhs, final float rhs) { public EqualsBuilder append(final float lhs, final float rhs) {
@ -504,10 +504,10 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Test if two <code>booleans</code>s are equal.</p> * <p>Test if two {@code booleans}s are equal.</p>
* *
* @param lhs the left hand <code>boolean</code> * @param lhs the left hand {@code boolean}
* @param rhs the right hand <code>boolean</code> * @param rhs the right hand {@code boolean}
* @return EqualsBuilder - used to chain calls. * @return EqualsBuilder - used to chain calls.
*/ */
public EqualsBuilder append(final boolean lhs, final boolean rhs) { public EqualsBuilder append(final boolean lhs, final boolean rhs) {
@ -519,7 +519,7 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Returns <code>true</code> if the fields that have been checked * <p>Returns {@code true} if the fields that have been checked
* are all equal.</p> * are all equal.</p>
* *
* @return boolean * @return boolean
@ -529,11 +529,11 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* <p>Returns <code>true</code> if the fields that have been checked * <p>Returns {@code true} if the fields that have been checked
* are all equal.</p> * are all equal.</p>
* *
* @return <code>true</code> if all of the fields that have been checked * @return {@code true} if all of the fields that have been checked
* are equal, <code>false</code> otherwise. * are equal, {@code false} otherwise.
* @since 3.0 * @since 3.0
*/ */
@Override @Override
@ -542,7 +542,7 @@ public class EqualsBuilder implements Builder<Boolean> {
} }
/** /**
* Sets the <code>isEquals</code> value. * Sets the {@code isEquals} value.
* *
* @param isEquals The value to set. * @param isEquals The value to set.
* @return this * @return this

View File

@ -906,4 +906,19 @@ public class IterUtil {
// 当两个Iterable长度不一致时返回false // 当两个Iterable长度不一致时返回false
return false == (it1.hasNext() || it2.hasNext()); return false == (it1.hasNext() || it2.hasNext());
} }
/**
* 清空指定{@link Iterator}此方法遍历后调用{@link Iterator#remove()}移除每个元素
*
* @param iterator {@link Iterator}
* @since 5.7.23
*/
public static void clear(Iterator<?> iterator) {
if (null != iterator) {
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
}
}
} }

View File

@ -0,0 +1,152 @@
package cn.hutool.core.collection;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.util.ObjectUtil;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;
/**
* 唯一键的Set<br>
* 通过自定义唯一键通过{@link #uniqueGenerator}生成节点对象对应的键作为Map的key确定唯一<br>
* 此Set与HashSet不同的是HashSet依赖于{@link Object#equals(Object)}确定唯一<br>
* 但是很多时候我们无法对对象进行修改此时在外部定义一个唯一规则即可完成去重
* <pre>
* {@code Set<UniqueTestBean> set = new UniqueKeySet<>(UniqueTestBean::getId);}
* </pre>
*
* @param <K> 唯一键类型
* @param <V> 值对象
* @author looly
* @since 5.7.23
*/
public class UniqueKeySet<K, V> extends AbstractSet<V> implements Serializable {
private static final long serialVersionUID = 1L;
private Map<K, V> map;
private final Function<V, K> uniqueGenerator;
//region 构造
/**
* 构造
*
* @param uniqueGenerator 唯一键生成规则函数用于生成对象对应的唯一键
*/
public UniqueKeySet(Function<V, K> uniqueGenerator) {
this(false, uniqueGenerator);
}
/**
* 构造
*
* @param isLinked 是否保持加入顺序
* @param uniqueGenerator 唯一键生成规则函数用于生成对象对应的唯一键
*/
public UniqueKeySet(boolean isLinked, Function<V, K> uniqueGenerator) {
this(MapBuilder.create(isLinked), uniqueGenerator);
}
/**
* 构造
*
* @param initialCapacity 初始容量
* @param loadFactor 增长因子
* @param uniqueGenerator 唯一键生成规则函数用于生成对象对应的唯一键
*/
public UniqueKeySet(int initialCapacity, float loadFactor, Function<V, K> uniqueGenerator) {
this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor)), uniqueGenerator);
}
/**
* 构造
*
* @param builder 初始Map定义了Map类型
* @param uniqueGenerator 唯一键生成规则函数用于生成对象对应的唯一键
*/
public UniqueKeySet(MapBuilder<K, V> builder, Function<V, K> uniqueGenerator) {
this.map = builder.build();
this.uniqueGenerator = uniqueGenerator;
}
//endregion
@Override
public Iterator<V> iterator() {
return map.values().iterator();
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean contains(Object o) {
//noinspection unchecked
return map.containsKey(this.uniqueGenerator.apply((V) o));
}
@Override
public boolean add(V v) {
return null == map.put(this.uniqueGenerator.apply(v), v);
}
/**
* 加入值如果值已经存在则忽略之
*
* @param v
* @return 是否成功加入
*/
public boolean addIfAbsent(V v) {
return null == map.putIfAbsent(this.uniqueGenerator.apply(v), v);
}
/**
* 加入集合中所有的值如果值已经存在则忽略之
*
* @param c 集合
* @return 是否有一个或多个被加入成功
*/
public boolean addAllIfAbsent(Collection<? extends V> c) {
boolean modified = false;
for (V v : c)
if (addIfAbsent(v)) {
modified = true;
}
return modified;
}
@Override
public boolean remove(Object o) {
//noinspection unchecked
return null != map.remove(this.uniqueGenerator.apply((V) o));
}
@Override
public void clear() {
map.clear();
}
@Override
@SuppressWarnings("unchecked")
public UniqueKeySet<K, V> clone() {
try {
UniqueKeySet<K, V> newSet = (UniqueKeySet<K, V>) super.clone();
newSet.map = ObjectUtil.clone(this.map);
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
}

View File

@ -77,6 +77,8 @@ import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.atomic.AtomicLongArray;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;
/** /**
* 转换器登记中心 * 转换器登记中心
@ -389,11 +391,13 @@ public class ConverterRegistry implements Serializable {
defaultConverterMap.put(Integer.class, new NumberConverter(Integer.class)); defaultConverterMap.put(Integer.class, new NumberConverter(Integer.class));
defaultConverterMap.put(AtomicInteger.class, new NumberConverter(AtomicInteger.class));// since 3.0.8 defaultConverterMap.put(AtomicInteger.class, new NumberConverter(AtomicInteger.class));// since 3.0.8
defaultConverterMap.put(Long.class, new NumberConverter(Long.class)); defaultConverterMap.put(Long.class, new NumberConverter(Long.class));
defaultConverterMap.put(LongAdder.class, new NumberConverter(LongAdder.class));
defaultConverterMap.put(AtomicLong.class, new NumberConverter(AtomicLong.class));// since 3.0.8 defaultConverterMap.put(AtomicLong.class, new NumberConverter(AtomicLong.class));// since 3.0.8
defaultConverterMap.put(Byte.class, new NumberConverter(Byte.class)); defaultConverterMap.put(Byte.class, new NumberConverter(Byte.class));
defaultConverterMap.put(Short.class, new NumberConverter(Short.class)); defaultConverterMap.put(Short.class, new NumberConverter(Short.class));
defaultConverterMap.put(Float.class, new NumberConverter(Float.class)); defaultConverterMap.put(Float.class, new NumberConverter(Float.class));
defaultConverterMap.put(Double.class, new NumberConverter(Double.class)); defaultConverterMap.put(Double.class, new NumberConverter(Double.class));
defaultConverterMap.put(DoubleAdder.class, new NumberConverter(DoubleAdder.class));
defaultConverterMap.put(Character.class, new CharacterConverter()); defaultConverterMap.put(Character.class, new CharacterConverter());
defaultConverterMap.put(Boolean.class, new BooleanConverter()); defaultConverterMap.put(Boolean.class, new BooleanConverter());
defaultConverterMap.put(AtomicBoolean.class, new AtomicBooleanConverter());// since 3.0.8 defaultConverterMap.put(AtomicBoolean.class, new AtomicBooleanConverter());// since 3.0.8

View File

@ -188,7 +188,7 @@ public class NumberChineseFormatter {
Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)"); Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)");
final String chinese = thousandToChinese(amount, isUseTraditional); final String chinese = thousandToChinese(amount, isUseTraditional);
if(amount < 20 && amount > 10){ if(amount < 20 && amount >= 10){
// "十一"而非"一十一" // "十一"而非"一十一"
return chinese.substring(1); return chinese.substring(1);
} }

View File

@ -186,7 +186,7 @@ public class NumberConverter extends AbstractConverter<Number> {
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseFloat(valueStr); return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseFloat(valueStr);
} else if (Double.class == targetType) { } else if (Double.class == targetType) {
if (value instanceof Number) { if (value instanceof Number) {
return ((Number) value).doubleValue(); return NumberUtil.toDouble((Number) value);
} else if (value instanceof Boolean) { } else if (value instanceof Boolean) {
return BooleanUtil.toDoubleObj((Boolean) value); return BooleanUtil.toDoubleObj((Boolean) value);
} }
@ -194,7 +194,7 @@ public class NumberConverter extends AbstractConverter<Number> {
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseDouble(valueStr); return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseDouble(valueStr);
} else if (DoubleAdder.class == targetType) { } else if (DoubleAdder.class == targetType) {
//jdk8 新增 //jdk8 新增
final Number number = convert(value, Long.class, toStrFunc); final Number number = convert(value, Double.class, toStrFunc);
if (null != number) { if (null != number) {
final DoubleAdder doubleAdder = new DoubleAdder(); final DoubleAdder doubleAdder = new DoubleAdder();
doubleAdder.add(number.doubleValue()); doubleAdder.add(number.doubleValue());

View File

@ -22,6 +22,21 @@ import java.util.concurrent.atomic.AtomicInteger;
* 4. INC 自增计数器确保同一秒内产生objectId的唯一性 * 4. INC 自增计数器确保同一秒内产生objectId的唯一性
* </pre> * </pre>
* *
* <table summary="" border="1">
* <tr>
* <td>时间戳</td>
* <td>机器ID</td>
* <td>进程ID</td>
* <td>自增计数器</td>
* </tr>
* <tr>
* <td>4</td>
* <td>3</td>
* <td>2</td>
* <td>3</td>
* </tr>
* </table>
*
* 参考http://blog.csdn.net/qxc1281/article/details/54021882 * 参考http://blog.csdn.net/qxc1281/article/details/54021882
* *
* @author looly * @author looly

View File

@ -0,0 +1,46 @@
package cn.hutool.core.map;
import cn.hutool.core.util.ObjectUtil;
import java.util.Map;
/**
* 抽象的{@link Map.Entry}实现来自Guava<br>
* 实现了默认的{@link #equals(Object)}{@link #hashCode()}{@link #toString()}方法<br>
* 默认{@link #setValue(Object)}抛出异常
*
* @param <K> 键类型
* @param <V> 值类型
* @author Guava
* @since 5.7.23
*/
public abstract class AbsEntry<K, V> implements Map.Entry<K, V> {
@Override
public V setValue(V value) {
throw new UnsupportedOperationException("Entry is read only.");
}
@Override
public boolean equals(Object object) {
if (object instanceof Map.Entry) {
final Map.Entry<?, ?> that = (Map.Entry<?, ?>) object;
return ObjectUtil.equals(this.getKey(), that.getKey())
&& ObjectUtil.equals(this.getValue(), that.getValue());
}
return false;
}
@Override
public int hashCode() {
//copy from 1.8 HashMap.Node
K k = getKey();
V v = getValue();
return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode());
}
@Override
public String toString() {
return getKey() + "=" + getValue();
}
}

View File

@ -1,7 +1,5 @@
package cn.hutool.core.map; package cn.hutool.core.map;
import cn.hutool.core.util.StrUtil;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -15,7 +13,7 @@ import java.util.Map;
* @param <V> 值类型 * @param <V> 值类型
* @since 4.0.7 * @since 4.0.7
*/ */
public class CamelCaseLinkedMap<K, V> extends CustomKeyMap<K, V> { public class CamelCaseLinkedMap<K, V> extends CamelCaseMap<K, V> {
private static final long serialVersionUID = 4043263744224569870L; private static final long serialVersionUID = 4043263744224569870L;
// ------------------------------------------------------------------------- Constructor start // ------------------------------------------------------------------------- Constructor start
@ -48,7 +46,7 @@ public class CamelCaseLinkedMap<K, V> extends CustomKeyMap<K, V> {
* 构造 * 构造
* *
* @param loadFactor 加载因子 * @param loadFactor 加载因子
* @param m Map * @param m Map数据会被默认拷贝到一个新的LinkedHashMap中
*/ */
public CamelCaseLinkedMap(float loadFactor, Map<? extends K, ? extends V> m) { public CamelCaseLinkedMap(float loadFactor, Map<? extends K, ? extends V> m) {
this(m.size(), loadFactor); this(m.size(), loadFactor);
@ -65,18 +63,4 @@ public class CamelCaseLinkedMap<K, V> extends CustomKeyMap<K, V> {
super(new LinkedHashMap<>(initialCapacity, loadFactor)); super(new LinkedHashMap<>(initialCapacity, loadFactor));
} }
// ------------------------------------------------------------------------- Constructor end // ------------------------------------------------------------------------- Constructor end
/**
* 将Key转为驼峰风格如果key为字符串的话
*
* @param key KEY
* @return 驼峰Key
*/
@Override
protected Object customKey(Object key) {
if (key instanceof CharSequence) {
key = StrUtil.toCamelCase(key.toString());
}
return key;
}
} }

View File

@ -1,24 +1,24 @@
package cn.hutool.core.map; package cn.hutool.core.map;
import cn.hutool.core.util.StrUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import cn.hutool.core.util.StrUtil;
/** /**
* 驼峰Key风格的Map<br> * 驼峰Key风格的Map<br>
* 对KEY转换为驼峰get("int_value")和get("intValue")获得的值相同put进入的值也会被覆盖 * 对KEY转换为驼峰get("int_value")和get("intValue")获得的值相同put进入的值也会被覆盖
* *
* @author Looly
*
* @param <K> 键类型 * @param <K> 键类型
* @param <V> 值类型 * @param <V> 值类型
* @author Looly
* @since 4.0.7 * @since 4.0.7
*/ */
public class CamelCaseMap<K, V> extends CustomKeyMap<K, V> { public class CamelCaseMap<K, V> extends FuncKeyMap<K, V> {
private static final long serialVersionUID = 4043263744224569870L; private static final long serialVersionUID = 4043263744224569870L;
// ------------------------------------------------------------------------- Constructor start // ------------------------------------------------------------------------- Constructor start
/** /**
* 构造 * 构造
*/ */
@ -48,7 +48,7 @@ public class CamelCaseMap<K, V> extends CustomKeyMap<K, V> {
* 构造 * 构造
* *
* @param loadFactor 加载因子 * @param loadFactor 加载因子
* @param m Map * @param m 初始Map数据会被默认拷贝到一个新的HashMap中
*/ */
public CamelCaseMap(float loadFactor, Map<? extends K, ? extends V> m) { public CamelCaseMap(float loadFactor, Map<? extends K, ? extends V> m) {
this(m.size(), loadFactor); this(m.size(), loadFactor);
@ -62,21 +62,23 @@ public class CamelCaseMap<K, V> extends CustomKeyMap<K, V> {
* @param loadFactor 加载因子 * @param loadFactor 加载因子
*/ */
public CamelCaseMap(int initialCapacity, float loadFactor) { public CamelCaseMap(int initialCapacity, float loadFactor) {
super(new HashMap<>(initialCapacity, loadFactor)); this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor)));
} }
// ------------------------------------------------------------------------- Constructor end
/** /**
* 将Key转为驼峰风格如果key为字符串的话 * 构造<br>
* 注意此构造将传入的Map作为被包装的Map针对任何修改传入的Map都会被同样修改
* *
* @param key KEY * @param emptyMapBuilder Map构造器必须构造空的Map
* @return 驼峰Key
*/ */
@Override CamelCaseMap(MapBuilder<K, V> emptyMapBuilder) {
protected Object customKey(Object key) { super(emptyMapBuilder.build(), (key) -> {
if (key instanceof CharSequence) { if (key instanceof CharSequence) {
key = StrUtil.toCamelCase(key.toString()); key = StrUtil.toCamelCase(key.toString());
} }
return key; //noinspection unchecked
return (K) key;
});
} }
// ------------------------------------------------------------------------- Constructor end
} }

View File

@ -13,7 +13,7 @@ import java.util.Map;
* @param <V> 值类型 * @param <V> 值类型
* @since 3.3.1 * @since 3.3.1
*/ */
public class CaseInsensitiveLinkedMap<K, V> extends CustomKeyMap<K, V> { public class CaseInsensitiveLinkedMap<K, V> extends CaseInsensitiveMap<K, V> {
private static final long serialVersionUID = 4043263744224569870L; private static final long serialVersionUID = 4043263744224569870L;
// ------------------------------------------------------------------------- Constructor start // ------------------------------------------------------------------------- Constructor start
@ -64,18 +64,4 @@ public class CaseInsensitiveLinkedMap<K, V> extends CustomKeyMap<K, V> {
super(new LinkedHashMap<>(initialCapacity, loadFactor)); super(new LinkedHashMap<>(initialCapacity, loadFactor));
} }
// ------------------------------------------------------------------------- Constructor end // ------------------------------------------------------------------------- Constructor end
/**
* 将Key转为小写
*
* @param key KEY
* @return 小写KEY
*/
@Override
protected Object customKey(Object key) {
if (key instanceof CharSequence) {
key = key.toString().toLowerCase();
}
return key;
}
} }

View File

@ -13,7 +13,7 @@ import java.util.Map;
* @param <V> 值类型 * @param <V> 值类型
* @since 3.0.2 * @since 3.0.2
*/ */
public class CaseInsensitiveMap<K, V> extends CustomKeyMap<K, V> { public class CaseInsensitiveMap<K, V> extends FuncKeyMap<K, V> {
private static final long serialVersionUID = 4043263744224569870L; private static final long serialVersionUID = 4043263744224569870L;
//------------------------------------------------------------------------- Constructor start //------------------------------------------------------------------------- Constructor start
@ -34,9 +34,10 @@ public class CaseInsensitiveMap<K, V> extends CustomKeyMap<K, V> {
} }
/** /**
* 构造 * 构造<br>
* 注意此构造将传入的Map作为被包装的Map针对任何修改传入的Map都会被同样修改
* *
* @param m Map * @param m 被包装的自定义Map创建器
*/ */
public CaseInsensitiveMap(Map<? extends K, ? extends V> m) { public CaseInsensitiveMap(Map<? extends K, ? extends V> m) {
this(DEFAULT_LOAD_FACTOR, m); this(DEFAULT_LOAD_FACTOR, m);
@ -61,21 +62,23 @@ public class CaseInsensitiveMap<K, V> extends CustomKeyMap<K, V> {
* @param loadFactor 加载因子 * @param loadFactor 加载因子
*/ */
public CaseInsensitiveMap(int initialCapacity, float loadFactor) { public CaseInsensitiveMap(int initialCapacity, float loadFactor) {
super(new HashMap<>(initialCapacity, loadFactor)); this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor)));
} }
//------------------------------------------------------------------------- Constructor end
/** /**
* 将Key转为小写 * 构造<br>
* 注意此构造将传入的Map作为被包装的Map针对任何修改传入的Map都会被同样修改
* *
* @param key KEY * @param emptyMapBuilder 被包装的自定义Map创建器
* @return 小写KEY
*/ */
@Override CaseInsensitiveMap(MapBuilder<K, V> emptyMapBuilder) {
protected Object customKey(Object key) { super(emptyMapBuilder.build(), (key)->{
if (key instanceof CharSequence) { if (key instanceof CharSequence) {
key = key.toString().toLowerCase(); key = key.toString().toLowerCase();
} }
return key; //noinspection unchecked
return (K) key;
});
} }
//------------------------------------------------------------------------- Constructor end
} }

View File

@ -15,7 +15,7 @@ import java.util.TreeMap;
* @param <V> 值类型 * @param <V> 值类型
* @since 3.3.1 * @since 3.3.1
*/ */
public class CaseInsensitiveTreeMap<K, V> extends CustomKeyMap<K, V> { public class CaseInsensitiveTreeMap<K, V> extends CaseInsensitiveMap<K, V> {
private static final long serialVersionUID = 4043263744224569870L; private static final long serialVersionUID = 4043263744224569870L;
// ------------------------------------------------------------------------- Constructor start // ------------------------------------------------------------------------- Constructor start
@ -40,7 +40,7 @@ public class CaseInsensitiveTreeMap<K, V> extends CustomKeyMap<K, V> {
/** /**
* 构造 * 构造
* *
* @param m Map * @param m Map初始Map键值对会被复制到新的TreeMap中
* @since 3.1.2 * @since 3.1.2
*/ */
public CaseInsensitiveTreeMap(SortedMap<? extends K, ? extends V> m) { public CaseInsensitiveTreeMap(SortedMap<? extends K, ? extends V> m) {
@ -56,18 +56,4 @@ public class CaseInsensitiveTreeMap<K, V> extends CustomKeyMap<K, V> {
super(new TreeMap<>(comparator)); super(new TreeMap<>(comparator));
} }
// ------------------------------------------------------------------------- Constructor end // ------------------------------------------------------------------------- Constructor end
/**
* 将Key转为小写
*
* @param key KEY
* @return 小写KEY
*/
@Override
protected Object customKey(Object key) {
if (key instanceof CharSequence) {
key = key.toString().toLowerCase();
}
return key;
}
} }

View File

@ -18,11 +18,11 @@ public abstract class CustomKeyMap<K, V> extends MapWrapper<K, V> {
* 构造<br> * 构造<br>
* 通过传入一个Map从而确定Map的类型子类需创建一个空的Map而非传入一个已有Map否则值可能会被修改 * 通过传入一个Map从而确定Map的类型子类需创建一个空的Map而非传入一个已有Map否则值可能会被修改
* *
* @param m Map 被包装的Map * @param emptyMap Map 被包装的Map必须为空Map否则自定义key会无效
* @since 3.1.2 * @since 3.1.2
*/ */
public CustomKeyMap(Map<K, V> m) { public CustomKeyMap(Map<K, V> emptyMap) {
super(m); super(emptyMap);
} }
@Override @Override

View File

@ -3,7 +3,8 @@ package cn.hutool.core.map;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
/** /**
* 固定大小的{@link LinkedHashMap} 实现 * 固定大小的{@link LinkedHashMap} 实现<br>
* 注意此类非线程安全由于{@link #get(Object)}操作会修改链表的顺序结构因此也不可以使用读写锁
* *
* @author looly * @author looly
* *

View File

@ -19,13 +19,14 @@ public class FuncKeyMap<K, V> extends CustomKeyMap<K, V> {
// ------------------------------------------------------------------------- Constructor start // ------------------------------------------------------------------------- Constructor start
/** /**
* 构造 * 构造<br>
* 注意提供的Map中不能有键值对否则可能导致自定义key失效
* *
* @param m Map * @param emptyMap Map提供的空map
* @param keyFunc 自定义KEY的函数 * @param keyFunc 自定义KEY的函数
*/ */
public FuncKeyMap(Map<K, V> m, Function<Object, K> keyFunc) { public FuncKeyMap(Map<K, V> emptyMap, Function<Object, K> keyFunc) {
super(m); super(emptyMap);
this.keyFunc = keyFunc; this.keyFunc = keyFunc;
} }
// ------------------------------------------------------------------------- Constructor end // ------------------------------------------------------------------------- Constructor end

View File

@ -120,6 +120,17 @@ public class MapBuilder<K, V> implements Builder<Map<K,V>> {
return this; return this;
} }
/**
* 清空Map
*
* @return this
* @since 5.7.23
*/
public MapBuilder<K, V> clear() {
this.map.clear();
return this;
}
/** /**
* 创建后的map * 创建后的map
* *

View File

@ -88,7 +88,6 @@ public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, S
} }
@Override @Override
@SuppressWarnings("NullableProblems")
public void putAll(Map<? extends K, ? extends V> m) { public void putAll(Map<? extends K, ? extends V> m) {
raw.putAll(m); raw.putAll(m);
} }
@ -99,25 +98,21 @@ public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, S
} }
@Override @Override
@SuppressWarnings("NullableProblems")
public Collection<V> values() { public Collection<V> values() {
return raw.values(); return raw.values();
} }
@Override @Override
@SuppressWarnings("NullableProblems")
public Set<K> keySet() { public Set<K> keySet() {
return raw.keySet(); return raw.keySet();
} }
@Override @Override
@SuppressWarnings("NullableProblems")
public Set<Entry<K, V>> entrySet() { public Set<Entry<K, V>> entrySet() {
return raw.entrySet(); return raw.entrySet();
} }
@Override @Override
@SuppressWarnings("NullableProblems")
public Iterator<Entry<K, V>> iterator() { public Iterator<Entry<K, V>> iterator() {
return this.entrySet().iterator(); return this.entrySet().iterator();
} }

View File

@ -0,0 +1,31 @@
package cn.hutool.core.map;
/**
* {@link java.util.Map.Entry}简单实现<br>
* 键值对使用不可变字段表示
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
* @since 5.7.23
*/
public class SimpleEntry<K, V> extends AbsEntry<K, V> {
private final K key;
private final V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
}

View File

@ -13,7 +13,6 @@ import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
@ -173,7 +172,7 @@ public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Ser
public Set<Map.Entry<K, V>> entrySet() { public Set<Map.Entry<K, V>> entrySet() {
final Set<Map.Entry<K, V>> hashSet = new LinkedHashSet<>(); final Set<Map.Entry<K, V>> hashSet = new LinkedHashSet<>();
for (int i = 0; i < size(); i++) { for (int i = 0; i < size(); i++) {
hashSet.add(new Entry<>(keys.get(i), values.get(i))); hashSet.add(new SimpleEntry<>(keys.get(i), values.get(i)));
} }
return hashSet; return hashSet;
} }
@ -191,7 +190,7 @@ public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Ser
@Override @Override
public Map.Entry<K, V> next() { public Map.Entry<K, V> next() {
return new Entry<>(keysIter.next(), valuesIter.next()); return new SimpleEntry<>(keysIter.next(), valuesIter.next());
} }
@Override @Override
@ -209,48 +208,4 @@ public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Ser
", values=" + values + ", values=" + values +
'}'; '}';
} }
private static class Entry<K, V> implements Map.Entry<K, V> {
private final K key;
private final V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
throw new UnsupportedOperationException("setValue not supported.");
}
@Override
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
return Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue());
}
return false;
}
@Override
public int hashCode() {
//copy from 1.8 HashMap.Node
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
}
} }

View File

@ -0,0 +1,236 @@
package cn.hutool.core.map.multi;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.collection.TransIter;
import cn.hutool.core.util.ObjectUtil;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* 抽象{@link Table}接口实现<br>
* 默认实现了
* <ul>
* <li>{@link #equals(Object)}</li>
* <li>{@link #hashCode()}</li>
* <li>{@link #toString()}</li>
* <li>{@link #values()}</li>
* <li>{@link #cellSet()}</li>
* <li>{@link #iterator()}</li>
* </ul>
*
* @param <R> 行类型
* @param <C> 列类型
* @param <V> 值类型
* @author Guava, Looly
* @since 5.7.23
*/
public abstract class AbsTable<R, C, V> implements Table<R, C, V> {
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof Table) {
final Table<?, ?, ?> that = (Table<?, ?, ?>) obj;
return this.cellSet().equals(that.cellSet());
} else {
return false;
}
}
@Override
public int hashCode() {
return cellSet().hashCode();
}
@Override
public String toString() {
return rowMap().toString();
}
//region values
@Override
public Collection<V> values() {
Collection<V> result = values;
return (result == null) ? values = new Values() : result;
}
private Collection<V> values;
private class Values extends AbstractCollection<V> {
@Override
public Iterator<V> iterator() {
return new TransIter<>(cellSet().iterator(), Cell::getValue);
}
@Override
public boolean contains(Object o) {
//noinspection unchecked
return containsValue((V) o);
}
@Override
public void clear() {
AbsTable.this.clear();
}
@Override
public int size() {
return AbsTable.this.size();
}
}
//endregion
//region cellSet
@Override
public Set<Cell<R, C, V>> cellSet() {
Set<Cell<R, C, V>> result = cellSet;
return (result == null) ? cellSet = new CellSet() : result;
}
private Set<Cell<R, C, V>> cellSet;
private class CellSet extends AbstractSet<Cell<R, C, V>> {
@Override
public boolean contains(Object o) {
if (o instanceof Cell) {
@SuppressWarnings("unchecked") final Cell<R, C, V> cell = (Cell<R, C, V>) o;
Map<C, V> row = getRow(cell.getRowKey());
if (null != row) {
return ObjectUtil.equals(row.get(cell.getColumnKey()), cell.getValue());
}
}
return false;
}
@Override
public boolean remove(Object o) {
if (contains(o)) {
@SuppressWarnings("unchecked") final Cell<R, C, V> cell = (Cell<R, C, V>) o;
AbsTable.this.remove(cell.getRowKey(), cell.getColumnKey());
}
return false;
}
@Override
public void clear() {
AbsTable.this.clear();
}
@Override
public Iterator<Table.Cell<R, C, V>> iterator() {
return new AbsTable<R, C, V>.CellIterator();
}
@Override
public int size() {
return AbsTable.this.size();
}
}
//endregion
//region iterator
@Override
public Iterator<Cell<R, C, V>> iterator() {
return new CellIterator();
}
/**
* 基于{@link Cell}{@link Iterator}实现
*/
private class CellIterator implements Iterator<Cell<R, C, V>> {
final Iterator<Map.Entry<R, Map<C, V>>> rowIterator = rowMap().entrySet().iterator();
Map.Entry<R, Map<C, V>> rowEntry;
Iterator<Map.Entry<C, V>> columnIterator = IterUtil.empty();
@Override
public boolean hasNext() {
return rowIterator.hasNext() || columnIterator.hasNext();
}
@Override
public Cell<R, C, V> next() {
if (false == columnIterator.hasNext()) {
rowEntry = rowIterator.next();
columnIterator = rowEntry.getValue().entrySet().iterator();
}
final Map.Entry<C, V> columnEntry = columnIterator.next();
return new SimpleCell<>(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue());
}
@Override
public void remove() {
columnIterator.remove();
if (rowEntry.getValue().isEmpty()) {
rowIterator.remove();
}
}
}
//endregion
/**
* 简单{@link Cell} 实现
*
* @param <R> 行类型
* @param <C> 列类型
* @param <V> 值类型
*/
private static class SimpleCell<R, C, V> implements Cell<R, C, V>, Serializable {
private static final long serialVersionUID = 1L;
private final R rowKey;
private final C columnKey;
private final V value;
SimpleCell(R rowKey, C columnKey, V value) {
this.rowKey = rowKey;
this.columnKey = columnKey;
this.value = value;
}
@Override
public R getRowKey() {
return rowKey;
}
@Override
public C getColumnKey() {
return columnKey;
}
@Override
public V getValue() {
return value;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Cell) {
Cell<?, ?, ?> other = (Cell<?, ?, ?>) obj;
return ObjectUtil.equal(rowKey, other.getRowKey())
&& ObjectUtil.equal(columnKey, other.getColumnKey())
&& ObjectUtil.equal(value, other.getValue());
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(rowKey, columnKey, value);
}
@Override
public String toString() {
return "(" + rowKey + "," + columnKey + ")=" + value;
}
}
}

View File

@ -0,0 +1,259 @@
package cn.hutool.core.map.multi;
import cn.hutool.core.builder.Builder;
import cn.hutool.core.collection.ComputeIter;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.collection.TransIter;
import cn.hutool.core.map.AbsEntry;
import cn.hutool.core.map.SimpleEntry;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* 将行的键作为主键的{@link Table}实现<br>
* 此结构为: =(=)
*
* @param <R> 行类型
* @param <C> 列类型
* @param <V> 值类型
* @author Guava, Looly
* @since 5.7.23
*/
public class RowKeyTable<R, C, V> extends AbsTable<R, C, V> {
final Map<R, Map<C, V>> raw;
/**
* 列的Map创建器用于定义Table中Value对应Map类型
*/
final Builder<? extends Map<C, V>> columnBuilder;
//region 构造
/**
* 构造
*/
public RowKeyTable() {
this(new HashMap<>());
}
/**
* 构造
*
* @param raw 原始Map
*/
public RowKeyTable(Map<R, Map<C, V>> raw) {
this(raw, HashMap::new);
}
/**
* 构造
*
* @param raw 原始Map
* @param columnMapBuilder 列的map创建器
*/
public RowKeyTable(Map<R, Map<C, V>> raw, Builder<? extends Map<C, V>> columnMapBuilder) {
this.raw = raw;
this.columnBuilder = null == columnMapBuilder ? HashMap::new : columnMapBuilder;
}
//endregion
@Override
public Map<R, Map<C, V>> rowMap() {
return raw;
}
@Override
public V put(R rowKey, C columnKey, V value) {
return raw.computeIfAbsent(rowKey, (key) -> columnBuilder.build()).put(columnKey, value);
}
@Override
public V remove(R rowKey, C columnKey) {
final Map<C, V> map = getRow(rowKey);
if (null == map) {
return null;
}
final V value = map.remove(columnKey);
if (map.isEmpty()) {
raw.remove(rowKey);
}
return value;
}
@Override
public boolean isEmpty() {
return raw.isEmpty();
}
@Override
public void clear() {
this.raw.clear();
}
@Override
public boolean containsColumn(C columnKey) {
if (columnKey == null) {
return false;
}
for (Map<C, V> map : raw.values()) {
if (null != map && map.containsKey(columnKey)) {
return true;
}
}
return false;
}
//region columnMap
@Override
public Map<C, Map<R, V>> columnMap() {
Map<C, Map<R, V>> result = columnMap;
return (result == null) ? columnMap = new ColumnMap() : result;
}
private Map<C, Map<R, V>> columnMap;
private class ColumnMap extends AbstractMap<C, Map<R, V>> {
@Override
public Set<Entry<C, Map<R, V>>> entrySet() {
return new ColumnMapEntrySet();
}
}
private class ColumnMapEntrySet extends AbstractSet<Map.Entry<C, Map<R, V>>> {
private final Set<C> columnKeySet = columnKeySet();
@Override
public Iterator<Map.Entry<C, Map<R, V>>> iterator() {
return new TransIter<>(columnKeySet.iterator(),
c -> new SimpleEntry<>(c, getColumn(c)));
}
@Override
public int size() {
return columnKeySet.size();
}
}
//endregion
//region columnKeySet
@Override
public Set<C> columnKeySet() {
Set<C> result = columnKeySet;
return (result == null) ? columnKeySet = new ColumnKeySet() : result;
}
private Set<C> columnKeySet;
private class ColumnKeySet extends AbstractSet<C> {
@Override
public Iterator<C> iterator() {
return new ColumnKeyIterator();
}
@Override
public int size() {
return IterUtil.size(iterator());
}
}
private class ColumnKeyIterator extends ComputeIter<C> {
final Map<C, V> seen = columnBuilder.build();
final Iterator<Map<C, V>> mapIterator = raw.values().iterator();
Iterator<Map.Entry<C, V>> entryIterator = IterUtil.empty();
@Override
protected C computeNext() {
while (true) {
if (entryIterator.hasNext()) {
Map.Entry<C, V> entry = entryIterator.next();
if (false == seen.containsKey(entry.getKey())) {
seen.put(entry.getKey(), entry.getValue());
return entry.getKey();
}
} else if (mapIterator.hasNext()) {
entryIterator = mapIterator.next().entrySet().iterator();
} else {
return null;
}
}
}
}
//endregion
//region getColumn
@Override
public Map<R, V> getColumn(C columnKey) {
return new Column(columnKey);
}
private class Column extends AbstractMap<R, V> {
final C columnKey;
Column(C columnKey) {
this.columnKey = columnKey;
}
@Override
public Set<Entry<R, V>> entrySet() {
return new EntrySet();
}
private class EntrySet extends AbstractSet<Map.Entry<R, V>> {
@Override
public Iterator<Map.Entry<R, V>> iterator() {
return new EntrySetIterator();
}
@Override
public int size() {
int size = 0;
for (Map<C, V> map : raw.values()) {
if (map.containsKey(columnKey)) {
size++;
}
}
return size;
}
}
private class EntrySetIterator extends ComputeIter<Entry<R, V>> {
final Iterator<Entry<R, Map<C, V>>> iterator = raw.entrySet().iterator();
@Override
protected Entry<R, V> computeNext() {
while (iterator.hasNext()) {
final Entry<R, Map<C, V>> entry = iterator.next();
if (entry.getValue().containsKey(columnKey)) {
return new AbsEntry<R, V>() {
@Override
public R getKey() {
return entry.getKey();
}
@Override
public V getValue() {
return entry.getValue().get(columnKey);
}
@Override
public V setValue(V value) {
return entry.getValue().put(columnKey, value);
}
};
}
}
return null;
}
}
}
//endregion
}

View File

@ -0,0 +1,259 @@
package cn.hutool.core.map.multi;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.func.Consumer3;
import cn.hutool.core.map.MapUtil;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* 表格数据结构定义<br>
* 此结构类似于Guava的Table接口使用两个键映射到一个值类似于表格结构
*
* @param <R> 行键类型
* @param <C> 列键类型
* @param <V> 值类型
* @since 5.7.23
*/
public interface Table<R, C, V> extends Iterable<Table.Cell<R, C, V>> {
/**
* 是否包含指定行列的映射<br>
* 行和列任意一个不存在都会返回{@code false}如果行和列都存在值为{@code null}也会返回{@code true}
*
* @param rowKey 行键
* @param columnKey 列键
* @return 是否包含映射
*/
default boolean contains(R rowKey, C columnKey) {
return Opt.ofNullable(getRow(rowKey)).map((map) -> map.containsKey(columnKey)).get();
}
//region Row
/**
* 行是否存在
*
* @param rowKey 行键
* @return 行是否存在
*/
default boolean containsRow(R rowKey) {
return Opt.ofNullable(rowMap()).map((map) -> map.containsKey(rowKey)).get();
}
/**
* 获取行
*
* @param rowKey 行键
* @return 行映射返回的键为列键值为表格的值
*/
default Map<C, V> getRow(R rowKey) {
return Opt.ofNullable(rowMap()).map((map) -> map.get(rowKey)).get();
}
/**
* 返回所有行的key行的key不可重复
*
* @return 行键
*/
default Set<R> rowKeySet() {
return Opt.ofNullable(rowMap()).map(Map::keySet).get();
}
/**
* 返回行列对应的Map
*
* @return map键为行键值为列和值的对应map
*/
Map<R, Map<C, V>> rowMap();
//endregion
//region Column
/**
* 列是否存在
*
* @param columnKey 列键
* @return 列是否存在
*/
default boolean containsColumn(C columnKey) {
return Opt.ofNullable(columnMap()).map((map) -> map.containsKey(columnKey)).get();
}
/**
* 获取列
*
* @param columnKey 列键
* @return 列映射返回的键为行键值为表格的值
*/
default Map<R, V> getColumn(C columnKey) {
return Opt.ofNullable(columnMap()).map((map) -> map.get(columnKey)).get();
}
/**
* 返回所有列的key列的key不可重复
*
* @return 列set
*/
default Set<C> columnKeySet() {
return Opt.ofNullable(columnMap()).map(Map::keySet).get();
}
/**
* 返回列-行对应的map
*
* @return map键为列键值为行和值的对应map
*/
Map<C, Map<R, V>> columnMap();
//endregion
//region value
/**
* 指定值是否存在
*
* @param value
* @return
*/
default boolean containsValue(V value){
final Collection<Map<C, V>> rows = Opt.ofNullable(rowMap()).map(Map::values).get();
if(null != rows){
for (Map<C, V> row : rows) {
if (row.containsValue(value)) {
return true;
}
}
}
return false;
}
/**
* 获取指定值
*
* @param rowKey 行键
* @param columnKey 列键
* @return 如果值不存在返回{@code null}
*/
default V get(R rowKey, C columnKey) {
return Opt.ofNullable(getRow(rowKey)).map((map) -> map.get(columnKey)).get();
}
/**
* 所有行列值的集合
*
* @return 值的集合
*/
Collection<V> values();
//endregion
/**
* 所有单元格集合
*
* @return 单元格集合
*/
Set<Cell<R, C, V>> cellSet();
/**
* 为表格指定行列赋值如果不存在创建之存在则替换之返回原值
*
* @param rowKey 行键
* @param columnKey 列键
* @param value
* @return 原值不存在返回{@code null}
*/
V put(R rowKey, C columnKey, V value);
/**
* 批量加入
*
* @param table 其他table
*/
default void putAll(Table<? extends R, ? extends C, ? extends V> table){
if (null != table) {
for (Table.Cell<? extends R, ? extends C, ? extends V> cell : table.cellSet()) {
put(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
}
}
}
/**
* 移除指定值
*
* @param rowKey 行键
* @param columnKey 列键
* @return 移除的值如果值不存在返回{@code null}
*/
V remove(R rowKey, C columnKey);
/**
* 表格是否为空
*
* @return 是否为空
*/
boolean isEmpty();
/**
* 表格大小一般为单元格的个数
*
* @return 表格大小
*/
default int size(){
final Map<R, Map<C, V>> rowMap = rowMap();
if(MapUtil.isEmpty(rowMap)){
return 0;
}
int size = 0;
for (Map<C, V> map : rowMap.values()) {
size += map.size();
}
return size;
}
/**
* 清空表格
*/
void clear();
/**
* 遍历表格的单元格处理值
*
* @param consumer 单元格值处理器
*/
default void forEach(Consumer3<? super R, ? super C, ? super V> consumer) {
for (Cell<R, C, V> cell : this) {
consumer.accept(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
}
}
/**
* 单元格用于表示一个单元格的行列和值
*
* @param <R> 行键类型
* @param <C> 列键类型
* @param <V> 值类型
*/
interface Cell<R, C, V> {
/**
* 获取行键
*
* @return 行键
*/
R getRowKey();
/**
* 获取列键
*
* @return 列键
*/
C getColumnKey();
/**
* 获取值
*
* @return
*/
V getValue();
}
}

View File

@ -1,5 +1,5 @@
/** /**
* 列表类型值的Map实现 * 多参数类型的Map实现包括集合类型值的Map和Table
* *
* @author looly * @author looly
* *

View File

@ -8,7 +8,8 @@ import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
/** /**
* 可复用的字符串生成器非线程安全 * 可复用的字符串生成器非线程安全<br>
* TODO 6.x移除此类java8的StringBuilder非常完善了无需重写
* *
* @author Looly * @author Looly
* @since 4.0.0 * @since 4.0.0

View File

@ -20,35 +20,60 @@ public class ModifierUtil {
* @since 4.0.5 * @since 4.0.5
*/ */
public enum ModifierType { public enum ModifierType {
/** public修饰符所有类都能访问 */ /**
* public修饰符所有类都能访问
*/
PUBLIC(Modifier.PUBLIC), PUBLIC(Modifier.PUBLIC),
/** private修饰符只能被自己访问和修改 */ /**
* private修饰符只能被自己访问和修改
*/
PRIVATE(Modifier.PRIVATE), PRIVATE(Modifier.PRIVATE),
/** protected修饰符自身、子类及同一个包中类可以访问 */ /**
* protected修饰符自身子类及同一个包中类可以访问
*/
PROTECTED(Modifier.PROTECTED), PROTECTED(Modifier.PROTECTED),
/** static修饰符静态修饰符指定变量被所有对象共享即所有实例都可以使用该变量。变量属于这个类 */ /**
* static修饰符静态修饰符指定变量被所有对象共享即所有实例都可以使用该变量变量属于这个类
*/
STATIC(Modifier.STATIC), STATIC(Modifier.STATIC),
/** final修饰符最终修饰符指定此变量的值不能变使用在方法上表示不能被重载 */ /**
* final修饰符最终修饰符指定此变量的值不能变使用在方法上表示不能被重载
*/
FINAL(Modifier.FINAL), FINAL(Modifier.FINAL),
/** synchronized同步修饰符在多个线程中该修饰符用于在运行前对他所属的方法加锁以防止其他线程的访问运行结束后解锁。 */ /**
* synchronized同步修饰符在多个线程中该修饰符用于在运行前对他所属的方法加锁以防止其他线程的访问运行结束后解锁
*/
SYNCHRONIZED(Modifier.SYNCHRONIZED), SYNCHRONIZED(Modifier.SYNCHRONIZED),
/** (易失修饰符)指定该变量可以同时被几个线程控制和修改 */ /**
* 易失修饰符指定该变量可以同时被几个线程控制和修改
*/
VOLATILE(Modifier.VOLATILE), VOLATILE(Modifier.VOLATILE),
/** (过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量,序列化时忽略 */ /**
* 过度修饰符指定该变量是系统保留暂无特别作用的临时性变量序列化时忽略
*/
TRANSIENT(Modifier.TRANSIENT), TRANSIENT(Modifier.TRANSIENT),
/** native本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。 */ /**
* native本地修饰符指定此方法的方法体是用其他语言在程序外部编写的
*/
NATIVE(Modifier.NATIVE), NATIVE(Modifier.NATIVE),
/** abstract将一个类声明为抽象类没有实现的方法需要子类提供方法实现。 */ /**
* abstract将一个类声明为抽象类没有实现的方法需要子类提供方法实现
*/
ABSTRACT(Modifier.ABSTRACT), ABSTRACT(Modifier.ABSTRACT),
/** strictfp一旦使用了关键字strictfp来声明某个类、接口或者方法时那么在这个关键字所声明的范围内所有浮点运算都是精确的符合IEEE-754规范的。 */ /**
* strictfp一旦使用了关键字strictfp来声明某个类接口或者方法时那么在这个关键字所声明的范围内所有浮点运算都是精确的符合IEEE-754规范的
*/
STRICT(Modifier.STRICT); STRICT(Modifier.STRICT);
/** 修饰符枚举对应的int修饰符值 */ /**
* 修饰符枚举对应的int修饰符值
*/
private final int value; private final int value;
/** /**
* 构造 * 构造
*
* @param modifier 修饰符int表示{@link Modifier} * @param modifier 修饰符int表示{@link Modifier}
*/ */
ModifierType(int modifier) { ModifierType(int modifier) {
@ -57,6 +82,7 @@ public class ModifierUtil {
/** /**
* 获取修饰符枚举对应的int修饰符值值见{@link Modifier} * 获取修饰符枚举对应的int修饰符值值见{@link Modifier}
*
* @return 修饰符枚举对应的int修饰符值 * @return 修饰符枚举对应的int修饰符值
*/ */
public int getValue() { public int getValue() {
@ -226,9 +252,21 @@ public class ModifierUtil {
return clazz.isSynthetic(); return clazz.isSynthetic();
} }
/**
* 是否抽象方法
*
* @param method 方法
* @return 是否抽象方法
* @since 5.7.23
*/
public static boolean isAbstract(Method method) {
return hasModifier(method, ModifierType.ABSTRACT);
}
//-------------------------------------------------------------------------------------------------------- Private method start //-------------------------------------------------------------------------------------------------------- Private method start
/** /**
* 多个修饰符做操作表示同时存在多个修饰符 * 多个修饰符做操作表示同时存在多个修饰符
*
* @param modifierTypes 修饰符列表元素不能为空 * @param modifierTypes 修饰符列表元素不能为空
* @return 之后的修饰符 * @return 之后的修饰符
*/ */

View File

@ -3,6 +3,7 @@ package cn.hutool.core.util;
import cn.hutool.core.annotation.Alias; import cn.hutool.core.annotation.Alias;
import cn.hutool.core.bean.NullWrapperBean; import cn.hutool.core.bean.NullWrapperBean;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.UniqueKeySet;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
@ -17,6 +18,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -347,6 +349,7 @@ public class ReflectUtil {
/** /**
* 是否为父类引用字段<br> * 是否为父类引用字段<br>
* 当字段所在类是对象子类时对象中定义的非static的class会自动生成一个以"this$0"为名称的字段指向父类对象 * 当字段所在类是对象子类时对象中定义的非static的class会自动生成一个以"this$0"为名称的字段指向父类对象
*
* @param field 字段 * @param field 字段
* @return 是否为父类引用字段 * @return 是否为父类引用字段
* @since 5.7.20 * @since 5.7.20
@ -648,51 +651,47 @@ public class ReflectUtil {
*/ */
public static Method[] getMethods(Class<?> beanClass) throws SecurityException { public static Method[] getMethods(Class<?> beanClass) throws SecurityException {
Assert.notNull(beanClass); Assert.notNull(beanClass);
return METHODS_CACHE.get(beanClass, () -> getMethodsDirectly(beanClass, true)); return METHODS_CACHE.get(beanClass,
() -> getMethodsDirectly(beanClass, true, true));
} }
/** /**
* 获得一个类中所有方法列表直接反射获取无缓存 * 获得一个类中所有方法列表直接反射获取无缓存<br>
* 接口获取方法和默认方法获取的方法包括
* <ul>
* <li>本类中的所有方法包括static方法</li>
* <li>父类中的所有方法包括static方法</li>
* <li>Object中包括static方法</li>
* </ul>
* *
* @param beanClass * @param beanClass 类或接口
* @param withSuperClassMethods 是否包括父类的方法列表 * @param withSupers 是否包括父类或接口的方法列表
* @param withMethodFromObject 是否包括Object中的方法
* @return 方法列表 * @return 方法列表
* @throws SecurityException 安全检查异常 * @throws SecurityException 安全检查异常
*/ */
public static Method[] getMethodsDirectly(Class<?> beanClass, boolean withSuperClassMethods) throws SecurityException { public static Method[] getMethodsDirectly(Class<?> beanClass, boolean withSupers, boolean withMethodFromObject) throws SecurityException {
Assert.notNull(beanClass); Assert.notNull(beanClass);
Method[] allMethods = null; if (beanClass.isInterface()) {
// 对于接口直接调用Class.getMethods方法获取所有方法因为接口都是public方法
return withSupers ? beanClass.getMethods() : beanClass.getDeclaredMethods();
}
final UniqueKeySet<String, Method> result = new UniqueKeySet<>(true, ReflectUtil::getUniqueKey);
Class<?> searchType = beanClass; Class<?> searchType = beanClass;
Class<?>[] tempInterfaceType, interfaceType;
Method[] declaredMethods;
if (searchType.isInterface()) {
allMethods = searchType.getDeclaredMethods();
interfaceType = searchType.getInterfaces();
while (ArrayUtil.isNotEmpty(interfaceType)) {
tempInterfaceType = interfaceType;
for (int i = 0; i < tempInterfaceType.length; i++) {
Class<?> temp = tempInterfaceType[i];
allMethods = ArrayUtil.append(allMethods, temp.getDeclaredMethods());
if (i == 0 || ArrayUtil.isEmpty(interfaceType)) {
interfaceType = temp.getInterfaces();
} else {
interfaceType = ArrayUtil.append(interfaceType, temp.getInterfaces());
}
}
}
} else {
while (searchType != null) { while (searchType != null) {
declaredMethods = searchType.getDeclaredMethods(); if (false == withMethodFromObject && Object.class == searchType) {
if (null == allMethods) { break;
allMethods = declaredMethods;
} else {
allMethods = ArrayUtil.append(allMethods, declaredMethods);
} }
searchType = withSuperClassMethods ? searchType.getSuperclass() : null; result.addAllIfAbsent(Arrays.asList(searchType.getDeclaredMethods()));
result.addAllIfAbsent(getDefaultMethodsFromInterface(searchType));
searchType = (withSupers && false == searchType.isInterface()) ? searchType.getSuperclass() : null;
} }
}
return allMethods; return result.toArray(new Method[0]);
} }
/** /**
@ -1055,4 +1054,47 @@ public class ReflectUtil {
} }
return accessibleObject; return accessibleObject;
} }
/**
* 获取方法的唯一键结构为:
* <pre>
* 返回类型#方法名:参数1类型,参数2类型...
* </pre>
*
* @param method 方法
* @return 方法唯一键
*/
private static String getUniqueKey(Method method) {
final StringBuilder sb = new StringBuilder();
sb.append(method.getReturnType().getName()).append('#');
sb.append(method.getName());
Class<?>[] parameters = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
if (i == 0) {
sb.append(':');
} else {
sb.append(',');
}
sb.append(parameters[i].getName());
}
return sb.toString();
}
/**
* 获取类对应接口中的非抽象方法default方法
*
* @param clazz
* @return 方法列表
*/
private static List<Method> getDefaultMethodsFromInterface(Class<?> clazz) {
List<Method> result = new ArrayList<>();
for (Class<?> ifc : clazz.getInterfaces()) {
for (Method m : ifc.getMethods()) {
if (false == ModifierUtil.isAbstract(m)) {
result.add(m);
}
}
}
return result;
}
} }

View File

@ -5,6 +5,7 @@ import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.bean.copier.ValueProvider;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
@ -78,14 +79,15 @@ public class BeanUtilTest {
@Test @Test
public void fillBeanWithMapIgnoreCaseTest() { public void fillBeanWithMapIgnoreCaseTest() {
HashMap<String, Object> map = MapUtil.newHashMap(); Map<String, Object> map = MapBuilder.<String, Object>create()
map.put("Name", "Joe"); .put("Name", "Joe")
map.put("aGe", 12); .put("aGe", 12)
map.put("openId", "DFDFSDFWERWER"); .put("openId", "DFDFSDFWERWER")
.build();
SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false); SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false);
Assert.assertEquals(person.getName(), "Joe"); Assert.assertEquals("Joe", person.getName());
Assert.assertEquals(person.getAge(), 12); Assert.assertEquals(12, person.getAge());
Assert.assertEquals(person.getOpenid(), "DFDFSDFWERWER"); Assert.assertEquals("DFDFSDFWERWER", person.getOpenid());
} }
@Test @Test

View File

@ -0,0 +1,34 @@
package cn.hutool.core.collection;
import cn.hutool.core.lang.Console;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
import java.util.Set;
public class UniqueKeySetTest {
@Test
public void addTest(){
Set<UniqueTestBean> set = new UniqueKeySet<>(UniqueTestBean::getId);
set.add(new UniqueTestBean("id1", "张三", "地球"));
set.add(new UniqueTestBean("id2", "李四", "火星"));
// id重复替换之前的元素
set.add(new UniqueTestBean("id2", "王五", "木星"));
// 后两个ID重复
Assert.assertEquals(2, set.size());
set.forEach(Console::log);
}
@Data
@AllArgsConstructor
static class UniqueTestBean{
private String id;
private String name;
private String address;
}
}

View File

@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.atomic.AtomicLongArray;
import java.util.concurrent.atomic.DoubleAdder;
/** /**
* 类型转换工具单元测试 * 类型转换工具单元测试
@ -361,4 +362,25 @@ public class ConvertTest {
final float f = Convert.toFloat(value); final float f = Convert.toFloat(value);
Assert.assertEquals(406.1F, f, 2); Assert.assertEquals(406.1F, f, 2);
} }
@Test
public void floatToDoubleTest(){
float a = 0.45f;
double b = Convert.toDouble(a);
Assert.assertEquals(a, b, 5);
}
@Test
public void floatToDoubleAddrTest(){
float a = 0.45f;
final DoubleAdder adder = Convert.convert(DoubleAdder.class, a);
Assert.assertEquals(a, adder.doubleValue(), 5);
}
@Test
public void doubleToFloatTest(){
double a = 0.45f;
float b = Convert.toFloat(a);
Assert.assertEquals(a, b, 5);
}
} }

View File

@ -1,13 +1,15 @@
package cn.hutool.core.lang.test.bean; package cn.hutool.core.lang.test.bean;
import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
/** /**
* *
* @author 质量过关 * @author 质量过关
* *
*/ */
@Data
public class ExamInfoDict implements Serializable { public class ExamInfoDict implements Serializable {
private static final long serialVersionUID = 3640936499125004525L; private static final long serialVersionUID = 3640936499125004525L;
@ -18,49 +20,7 @@ public class ExamInfoDict implements Serializable {
// 试题是否作答 // 试题是否作答
private Integer answerIs; private Integer answerIs;
public Integer getId() {
return id;
}
public Integer getId(Integer defaultValue) { public Integer getId(Integer defaultValue) {
return this.id == null ? defaultValue : this.id; return this.id == null ? defaultValue : this.id;
} }
public void setId(Integer id) {
this.id = id;
}
public Integer getExamType() {
return examType;
}
public void setExamType(Integer examType) {
this.examType = examType;
}
public Integer getAnswerIs() {
return answerIs;
}
public void setAnswerIs(Integer answerIs) {
this.answerIs = answerIs;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ExamInfoDict that = (ExamInfoDict) o;
return Objects.equals(id, that.id) && Objects.equals(examType, that.examType) && Objects.equals(answerIs, that.answerIs);
}
@Override
public int hashCode() {
return Objects.hash(id, examType, answerIs);
}
@Override
public String toString() {
return "ExamInfoDict{" + "id=" + id + ", examType=" + examType + ", answerIs=" + answerIs + '}';
}
} }

View File

@ -0,0 +1,39 @@
package cn.hutool.core.map;
import cn.hutool.core.map.multi.RowKeyTable;
import cn.hutool.core.map.multi.Table;
import org.junit.Assert;
import org.junit.Test;
import java.util.Map;
public class RowKeyTableTest {
@Test
public void putGetTest(){
final Table<Integer, Integer, Integer> table = new RowKeyTable<>();
table.put(1, 2, 3);
table.put(1, 6, 4);
Assert.assertEquals(new Integer(3), table.get(1, 2));
Assert.assertNull(table.get(1, 3));
//判断row和column确定的二维点是否存在
Assert.assertTrue(table.contains(1, 2));
Assert.assertFalse(table.contains(1, 3));
//判断列
Assert.assertTrue(table.containsColumn(2));
Assert.assertFalse(table.containsColumn(3));
// 判断行
Assert.assertTrue(table.containsRow(1));
Assert.assertFalse(table.containsRow(2));
// 获取列
Map<Integer, Integer> column = table.getColumn(6);
Assert.assertEquals(1, column.size());
Assert.assertEquals(new Integer(4), column.get(1));
}
}

View File

@ -23,7 +23,7 @@ public class ReflectUtilTest {
@Test @Test
public void getMethodsTest() { public void getMethodsTest() {
Method[] methods = ReflectUtil.getMethods(ExamInfoDict.class); Method[] methods = ReflectUtil.getMethods(ExamInfoDict.class);
Assert.assertEquals(22, methods.length); Assert.assertEquals(20, methods.length);
//过滤器测试 //过滤器测试
methods = ReflectUtil.getMethods(ExamInfoDict.class, t -> Integer.class.equals(t.getReturnType())); methods = ReflectUtil.getMethods(ExamInfoDict.class, t -> Integer.class.equals(t.getReturnType()));
@ -35,7 +35,7 @@ public class ReflectUtilTest {
//null过滤器测试 //null过滤器测试
methods = ReflectUtil.getMethods(ExamInfoDict.class, null); methods = ReflectUtil.getMethods(ExamInfoDict.class, null);
Assert.assertEquals(22, methods.length); Assert.assertEquals(20, methods.length);
final Method method2 = methods[0]; final Method method2 = methods[0];
Assert.assertNotNull(method2); Assert.assertNotNull(method2);
} }
@ -116,7 +116,7 @@ public class ReflectUtilTest {
@Ignore @Ignore
public void getMethodBenchTest() { public void getMethodBenchTest() {
// 预热 // 预热
getMethod(TestBenchClass.class, false, "getH"); getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH");
final TimeInterval timer = DateUtil.timer(); final TimeInterval timer = DateUtil.timer();
timer.start(); timer.start();
@ -127,7 +127,7 @@ public class ReflectUtilTest {
timer.restart(); timer.restart();
for (int i = 0; i < 100000000; i++) { for (int i = 0; i < 100000000; i++) {
getMethod(TestBenchClass.class, false, "getH"); getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH");
} }
Console.log(timer.interval()); Console.log(timer.interval());
} }
@ -150,7 +150,7 @@ public class ReflectUtilTest {
private String n; private String n;
} }
public static Method getMethod(Class<?> clazz, boolean ignoreCase, String methodName, Class<?>... paramTypes) throws SecurityException { public static Method getMethodWithReturnTypeCheck(Class<?> clazz, boolean ignoreCase, String methodName, Class<?>... paramTypes) throws SecurityException {
if (null == clazz || StrUtil.isBlank(methodName)) { if (null == clazz || StrUtil.isBlank(methodName)) {
return null; return null;
} }
@ -169,4 +169,74 @@ public class ReflectUtilTest {
} }
return res; return res;
} }
@Test
public void getMethodsFromClassExtends() {
// 继承情况下需解决方法去重问题
Method[] methods = ReflectUtil.getMethods(C2.class);
Assert.assertEquals(15, methods.length);
// 排除Object中的方法
// 3个方法包括类
methods = ReflectUtil.getMethodsDirectly(C2.class, true, false);
Assert.assertEquals(3, methods.length);
// getA属于本类
Assert.assertEquals("public void cn.hutool.core.util.ReflectUtilTest$C2.getA()", methods[0].toString());
// getB属于父类
Assert.assertEquals("public void cn.hutool.core.util.ReflectUtilTest$C1.getB()", methods[1].toString());
// getC属于接口中的默认方法
Assert.assertEquals("public default void cn.hutool.core.util.ReflectUtilTest$TestInterface1.getC()", methods[2].toString());
}
@Test
public void getMethodsFromInterfaceTest() {
// 对于接口直接调用Class.getMethods方法获取所有方法因为接口都是public方法
// 因此此处得到包括TestInterface1TestInterface2TestInterface3中一共4个方法
final Method[] methods = ReflectUtil.getMethods(TestInterface3.class);
Assert.assertEquals(4, methods.length);
// 接口里调用getMethods和getPublicMethods效果相同
final Method[] publicMethods = ReflectUtil.getPublicMethods(TestInterface3.class);
Assert.assertArrayEquals(methods, publicMethods);
}
interface TestInterface1 {
void getA();
void getB();
default void getC() {
}
}
interface TestInterface2 extends TestInterface1 {
@Override
void getB();
}
interface TestInterface3 extends TestInterface2 {
void get3();
}
class C1 implements TestInterface2 {
@Override
public void getA() {
}
@Override
public void getB() {
}
}
class C2 extends C1 {
@Override
public void getA() {
}
}
} }

View File

@ -0,0 +1,157 @@
package cn.hutool.crypto.symmetric;
import cn.hutool.core.io.IoUtil;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
/**
* XXTEACorrected Block Tiny Encryption Algorithm算法实现<br>
* 来自https://github.com/xxtea/xxtea-java
*
* @author Ma Bingyao <mabingyao@gmail.com>
*/
public class XXTEA implements SymmetricEncryptor, SymmetricDecryptor, Serializable {
private static final long serialVersionUID = 1L;
private static final int DELTA = 0x9E3779B9;
private final byte[] key;
/**
* 构造
*
* @param key 密钥16位
*/
public XXTEA(byte[] key) {
this.key = key;
}
@Override
public byte[] encrypt(byte[] data) {
if (data.length == 0) {
return data;
}
return toByteArray(encrypt(
toIntArray(data, true),
toIntArray(fixKey(key), false)), false);
}
@Override
public void encrypt(InputStream data, OutputStream out, boolean isClose) {
IoUtil.write(out, isClose, encrypt(IoUtil.readBytes(data)));
}
@Override
public byte[] decrypt(byte[] data) {
if (data.length == 0) {
return data;
}
return toByteArray(decrypt(
toIntArray(data, false),
toIntArray(fixKey(key), false)), true);
}
@Override
public void decrypt(InputStream data, OutputStream out, boolean isClose) {
IoUtil.write(out, isClose, decrypt(IoUtil.readBytes(data)));
}
//region Private Method
private static int[] encrypt(int[] v, int[] k) {
int n = v.length - 1;
if (n < 1) {
return v;
}
int p, q = 6 + 52 / (n + 1);
int z = v[n], y, sum = 0, e;
while (q-- > 0) {
sum = sum + DELTA;
e = sum >>> 2 & 3;
for (p = 0; p < n; p++) {
y = v[p + 1];
z = v[p] += mx(sum, y, z, p, e, k);
}
y = v[0];
z = v[n] += mx(sum, y, z, p, e, k);
}
return v;
}
private static int[] decrypt(int[] v, int[] k) {
int n = v.length - 1;
if (n < 1) {
return v;
}
int p, q = 6 + 52 / (n + 1);
int z, y = v[0], sum = q * DELTA, e;
while (sum != 0) {
e = sum >>> 2 & 3;
for (p = n; p > 0; p--) {
z = v[p - 1];
y = v[p] -= mx(sum, y, z, p, e, k);
}
z = v[n];
y = v[0] -= mx(sum, y, z, p, e, k);
sum = sum - DELTA;
}
return v;
}
private static int mx(int sum, int y, int z, int p, int e, int[] k) {
return (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
}
private static byte[] fixKey(byte[] key) {
if (key.length == 16) {
return key;
}
byte[] fixedkey = new byte[16];
System.arraycopy(key, 0, fixedkey, 0, Math.min(key.length, 16));
return fixedkey;
}
private static int[] toIntArray(byte[] data, boolean includeLength) {
int n = (((data.length & 3) == 0)
? (data.length >>> 2)
: ((data.length >>> 2) + 1));
int[] result;
if (includeLength) {
result = new int[n + 1];
result[n] = data.length;
} else {
result = new int[n];
}
n = data.length;
for (int i = 0; i < n; ++i) {
result[i >>> 2] |= (0x000000ff & data[i]) << ((i & 3) << 3);
}
return result;
}
private static byte[] toByteArray(int[] data, boolean includeLength) {
int n = data.length << 2;
if (includeLength) {
int m = data[data.length - 1];
n -= 4;
if ((m < n - 3) || (m > n)) {
return null;
}
n = m;
}
byte[] result = new byte[n];
for (int i = 0; i < n; ++i) {
result[i] = (byte) (data[i >>> 2] >>> ((i & 3) << 3));
}
return result;
}
//endregion
}

View File

@ -0,0 +1,54 @@
package cn.hutool.crypto.test.symmetric;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import cn.hutool.crypto.symmetric.XXTEA;
import org.junit.Assert;
import org.junit.Test;
/**
* TEATiny Encryption Algorithm XTEA算法单元测试
*/
public class TEATest {
@Test
public void teaTest() {
String data = "测试的加密数据 by Hutool";
// 密钥必须为128bit
final SymmetricCrypto tea = new SymmetricCrypto("TEA", "MyPassword123456".getBytes());
final byte[] encrypt = tea.encrypt(data);
// 解密
final String decryptStr = tea.decryptStr(encrypt);
Assert.assertEquals(data, decryptStr);
}
@Test
public void xteaTest() {
String data = "测试的加密数据 by Hutool";
// 密钥必须为128bit
final SymmetricCrypto tea = new SymmetricCrypto("XTEA", "MyPassword123456".getBytes());
final byte[] encrypt = tea.encrypt(data);
// 解密
final String decryptStr = tea.decryptStr(encrypt);
Assert.assertEquals(data, decryptStr);
}
@Test
public void xxteaTest() {
String data = "测试的加密数据 by Hutool";
// 密钥必须为128bit
final XXTEA tea = new XXTEA("MyPassword123456".getBytes());
final byte[] encrypt = tea.encrypt(data);
// 解密
final String decryptStr = tea.decryptStr(encrypt);
Assert.assertEquals(data, decryptStr);
}
}

View File

@ -42,11 +42,11 @@ public class ScriptRuntimeException extends RuntimeException {
} }
/** /**
* Creates a <code>ScriptException</code> with message, filename and linenumber to be used in error messages. * Creates a {@code ScriptException} with message, filename and linenumber to be used in error messages.
* *
* @param message The string to use in the message * @param message The string to use in the message
* @param fileName The file or resource name describing the location of a script error causing the <code>ScriptException</code> to be thrown. * @param fileName The file or resource name describing the location of a script error causing the {@code ScriptException} to be thrown.
* @param lineNumber A line number describing the location of a script error causing the <code>ScriptException</code> to be thrown. * @param lineNumber A line number describing the location of a script error causing the {@code ScriptException} to be thrown.
*/ */
public ScriptRuntimeException(String message, String fileName, int lineNumber) { public ScriptRuntimeException(String message, String fileName, int lineNumber) {
super(message); super(message);
@ -55,7 +55,7 @@ public class ScriptRuntimeException extends RuntimeException {
} }
/** /**
* <code>ScriptException</code> constructor specifying message, filename, line number and column number. * {@code ScriptException} constructor specifying message, filename, line number and column number.
* *
* @param message The message. * @param message The message.
* @param fileName The filename * @param fileName The filename