mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
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:
commit
88a9fc4a37
@ -2,14 +2,20 @@
|
||||
# 🚀Changelog
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.7.23 (2022-03-04)
|
||||
# 5.7.23 (2022-03-09)
|
||||
|
||||
### 🐣新特性
|
||||
* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee)
|
||||
* 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@Gitee)
|
||||
* 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee)
|
||||
* 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee)
|
||||
* 【core 】 增加Table实现(issue#2179@Github)
|
||||
* 【core 】 增加UniqueKeySet(issue#I4WUWR@Gitee)
|
||||
*
|
||||
### 🐞Bug修复
|
||||
* 【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)
|
||||
|
@ -39,7 +39,7 @@ public class AnnotationProxy<T extends Annotation> implements Annotation, Invoca
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return null;
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -225,8 +225,8 @@ public class BeanUtil {
|
||||
*/
|
||||
private static Map<String, PropertyDescriptor> internalGetPropertyDescriptorMap(Class<?> clazz, boolean ignoreCase) throws BeanException {
|
||||
final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz);
|
||||
final Map<String, PropertyDescriptor> map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1)
|
||||
: new HashMap<>((int) (propertyDescriptors.length), 1);
|
||||
final Map<String, PropertyDescriptor> map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1f)
|
||||
: new HashMap<>(propertyDescriptors.length, 1);
|
||||
|
||||
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
|
||||
map.put(propertyDescriptor.getName(), propertyDescriptor);
|
||||
|
@ -70,7 +70,7 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
* Converters value pair into a register pair.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param lhs {@code this} object
|
||||
* @param rhs the other object
|
||||
* @return the pair
|
||||
*/
|
||||
@ -82,15 +82,15 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
|
||||
/**
|
||||
* <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.
|
||||
* Objects might be swapped therefore a check is needed if the object pair
|
||||
* is registered in given or swapped order.
|
||||
* </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
|
||||
* @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
|
||||
*/
|
||||
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.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs <code>this</code> object to register
|
||||
* @param lhs {@code this} object to register
|
||||
* @param rhs the other object to register
|
||||
*/
|
||||
static void register(final Object lhs, final Object rhs) {
|
||||
@ -131,7 +131,7 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
* <p>
|
||||
* 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
|
||||
* @since 3.0
|
||||
*/
|
||||
@ -170,7 +170,7 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
* @param lhs 此对象
|
||||
* @param rhs 另一个对象
|
||||
* @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) {
|
||||
return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class));
|
||||
@ -182,62 +182,62 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
* @param lhs 此对象
|
||||
* @param rhs 另一个对象
|
||||
* @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) {
|
||||
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>
|
||||
*
|
||||
* <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
|
||||
* 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
|
||||
* <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
|
||||
* 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>
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param lhs {@code this} object
|
||||
* @param rhs the other object
|
||||
* @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) {
|
||||
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>
|
||||
*
|
||||
* <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
|
||||
* 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
|
||||
* <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
|
||||
* 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
|
||||
* up to and including the specified superclass. A null superclass is treated
|
||||
* as java.lang.Object.</p>
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param lhs {@code this} object
|
||||
* @param rhs the other object
|
||||
* @param testTransients whether to include transient fields
|
||||
* @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
|
||||
* @return <code>true</code> if the two Objects have tested equals.
|
||||
* @return {@code true} if the two Objects have tested equals.
|
||||
* @since 2.0
|
||||
*/
|
||||
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.
|
||||
* @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
|
||||
* <code>equals</code> method.</p>
|
||||
* <p>Test if two {@code Object}s are equal using their
|
||||
* {@code equals} method.</p>
|
||||
*
|
||||
* @param lhs the left hand object
|
||||
* @param rhs the right hand object
|
||||
@ -388,11 +388,11 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test if two <code>long</code> s are equal.
|
||||
* Test if two {@code long} s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs the left hand <code>long</code>
|
||||
* @param rhs the right hand <code>long</code>
|
||||
* @param lhs the left hand {@code long}
|
||||
* @param rhs the right hand {@code long}
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
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 rhs the right hand <code>int</code>
|
||||
* @param lhs the left hand {@code int}
|
||||
* @param rhs the right hand {@code int}
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
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 rhs the right hand <code>short</code>
|
||||
* @param lhs the left hand {@code short}
|
||||
* @param rhs the right hand {@code short}
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
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 rhs the right hand <code>char</code>
|
||||
* @param lhs the left hand {@code char}
|
||||
* @param rhs the right hand {@code char}
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
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 rhs the right hand <code>byte</code>
|
||||
* @param lhs the left hand {@code byte}
|
||||
* @param rhs the right hand {@code byte}
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
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
|
||||
* pattern of bits returned by <code>doubleToLong</code> are equal.</p>
|
||||
* <p>Test if two {@code double}s are equal by testing that the
|
||||
* 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
|
||||
* <code>HashCodeBuilder</code>.</p>
|
||||
* {@code HashCodeBuilder}.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>double</code>
|
||||
* @param rhs the right hand <code>double</code>
|
||||
* @param lhs the left hand {@code double}
|
||||
* @param rhs the right hand {@code double}
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
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>
|
||||
*
|
||||
* <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
|
||||
* <code>HashCodeBuilder</code>.</p>
|
||||
* {@code HashCodeBuilder}.</p>
|
||||
*
|
||||
* @param lhs the left hand <code>float</code>
|
||||
* @param rhs the right hand <code>float</code>
|
||||
* @param lhs the left hand {@code float}
|
||||
* @param rhs the right hand {@code float}
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
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 rhs the right hand <code>boolean</code>
|
||||
* @param lhs the left hand {@code boolean}
|
||||
* @param rhs the right hand {@code boolean}
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
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>
|
||||
*
|
||||
* @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>
|
||||
*
|
||||
* @return <code>true</code> if all of the fields that have been checked
|
||||
* are equal, <code>false</code> otherwise.
|
||||
* @return {@code true} if all of the fields that have been checked
|
||||
* are equal, {@code false} otherwise.
|
||||
* @since 3.0
|
||||
*/
|
||||
@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.
|
||||
* @return this
|
||||
|
@ -906,4 +906,19 @@ public class IterUtil {
|
||||
// 当两个Iterable长度不一致时返回false
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -77,6 +77,8 @@ import java.util.concurrent.atomic.AtomicIntegerArray;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicLongArray;
|
||||
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(AtomicInteger.class, new NumberConverter(AtomicInteger.class));// since 3.0.8
|
||||
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(Byte.class, new NumberConverter(Byte.class));
|
||||
defaultConverterMap.put(Short.class, new NumberConverter(Short.class));
|
||||
defaultConverterMap.put(Float.class, new NumberConverter(Float.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(Boolean.class, new BooleanConverter());
|
||||
defaultConverterMap.put(AtomicBoolean.class, new AtomicBooleanConverter());// since 3.0.8
|
||||
|
@ -188,7 +188,7 @@ public class NumberChineseFormatter {
|
||||
Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)!");
|
||||
|
||||
final String chinese = thousandToChinese(amount, isUseTraditional);
|
||||
if(amount < 20 && amount > 10){
|
||||
if(amount < 20 && amount >= 10){
|
||||
// "十一"而非"一十一"
|
||||
return chinese.substring(1);
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseFloat(valueStr);
|
||||
} else if (Double.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
return NumberUtil.toDouble((Number) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toDoubleObj((Boolean) value);
|
||||
}
|
||||
@ -194,7 +194,7 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseDouble(valueStr);
|
||||
} else if (DoubleAdder.class == targetType) {
|
||||
//jdk8 新增
|
||||
final Number number = convert(value, Long.class, toStrFunc);
|
||||
final Number number = convert(value, Double.class, toStrFunc);
|
||||
if (null != number) {
|
||||
final DoubleAdder doubleAdder = new DoubleAdder();
|
||||
doubleAdder.add(number.doubleValue());
|
||||
|
@ -22,6 +22,21 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* 4. INC 自增计数器。确保同一秒内产生objectId的唯一性。
|
||||
* </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
|
||||
*
|
||||
* @author looly
|
||||
|
46
hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java
Normal file
46
hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java
Normal 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();
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package cn.hutool.core.map;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -15,7 +13,7 @@ import java.util.Map;
|
||||
* @param <V> 值类型
|
||||
* @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;
|
||||
|
||||
// ------------------------------------------------------------------------- Constructor start
|
||||
@ -48,7 +46,7 @@ public class CamelCaseLinkedMap<K, V> extends CustomKeyMap<K, V> {
|
||||
* 构造
|
||||
*
|
||||
* @param loadFactor 加载因子
|
||||
* @param m Map
|
||||
* @param m Map,数据会被默认拷贝到一个新的LinkedHashMap中
|
||||
*/
|
||||
public CamelCaseLinkedMap(float loadFactor, Map<? extends K, ? extends V> m) {
|
||||
this(m.size(), loadFactor);
|
||||
@ -65,18 +63,4 @@ public class CamelCaseLinkedMap<K, V> extends CustomKeyMap<K, V> {
|
||||
super(new LinkedHashMap<>(initialCapacity, loadFactor));
|
||||
}
|
||||
// ------------------------------------------------------------------------- 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;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
package cn.hutool.core.map;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 驼峰Key风格的Map<br>
|
||||
* 对KEY转换为驼峰,get("int_value")和get("intValue")获得的值相同,put进入的值也会被覆盖
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author Looly
|
||||
* @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;
|
||||
|
||||
// ------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
@ -48,7 +48,7 @@ public class CamelCaseMap<K, V> extends CustomKeyMap<K, V> {
|
||||
* 构造
|
||||
*
|
||||
* @param loadFactor 加载因子
|
||||
* @param m Map
|
||||
* @param m 初始Map,数据会被默认拷贝到一个新的HashMap中
|
||||
*/
|
||||
public CamelCaseMap(float loadFactor, Map<? extends K, ? extends V> m) {
|
||||
this(m.size(), loadFactor);
|
||||
@ -59,24 +59,26 @@ public class CamelCaseMap<K, V> extends CustomKeyMap<K, V> {
|
||||
* 构造
|
||||
*
|
||||
* @param initialCapacity 初始大小
|
||||
* @param loadFactor 加载因子
|
||||
* @param 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
|
||||
* @return 驼峰Key
|
||||
* @param emptyMapBuilder Map构造器,必须构造空的Map
|
||||
*/
|
||||
@Override
|
||||
protected Object customKey(Object key) {
|
||||
if (key instanceof CharSequence) {
|
||||
key = StrUtil.toCamelCase(key.toString());
|
||||
}
|
||||
return key;
|
||||
CamelCaseMap(MapBuilder<K, V> emptyMapBuilder) {
|
||||
super(emptyMapBuilder.build(), (key) -> {
|
||||
if (key instanceof CharSequence) {
|
||||
key = StrUtil.toCamelCase(key.toString());
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (K) key;
|
||||
});
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import java.util.Map;
|
||||
* @param <V> 值类型
|
||||
* @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;
|
||||
|
||||
// ------------------------------------------------------------------------- Constructor start
|
||||
@ -64,18 +64,4 @@ public class CaseInsensitiveLinkedMap<K, V> extends CustomKeyMap<K, V> {
|
||||
super(new LinkedHashMap<>(initialCapacity, loadFactor));
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
|
||||
/**
|
||||
* 将Key转为小写
|
||||
*
|
||||
* @param key KEY
|
||||
* @return 小写KEY
|
||||
*/
|
||||
@Override
|
||||
protected Object customKey(Object key) {
|
||||
if (key instanceof CharSequence) {
|
||||
key = key.toString().toLowerCase();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import java.util.Map;
|
||||
* @param <V> 值类型
|
||||
* @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;
|
||||
|
||||
//------------------------------------------------------------------------- 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) {
|
||||
this(DEFAULT_LOAD_FACTOR, m);
|
||||
@ -61,21 +62,23 @@ public class CaseInsensitiveMap<K, V> extends CustomKeyMap<K, V> {
|
||||
* @param 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
|
||||
* @return 小写KEY
|
||||
* @param emptyMapBuilder 被包装的自定义Map创建器
|
||||
*/
|
||||
@Override
|
||||
protected Object customKey(Object key) {
|
||||
if (key instanceof CharSequence) {
|
||||
key = key.toString().toLowerCase();
|
||||
}
|
||||
return key;
|
||||
CaseInsensitiveMap(MapBuilder<K, V> emptyMapBuilder) {
|
||||
super(emptyMapBuilder.build(), (key)->{
|
||||
if (key instanceof CharSequence) {
|
||||
key = key.toString().toLowerCase();
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (K) key;
|
||||
});
|
||||
}
|
||||
//------------------------------------------------------------------------- Constructor end
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import java.util.TreeMap;
|
||||
* @param <V> 值类型
|
||||
* @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;
|
||||
|
||||
// ------------------------------------------------------------------------- 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
|
||||
*/
|
||||
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));
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
|
||||
/**
|
||||
* 将Key转为小写
|
||||
*
|
||||
* @param key KEY
|
||||
* @return 小写KEY
|
||||
*/
|
||||
@Override
|
||||
protected Object customKey(Object key) {
|
||||
if (key instanceof CharSequence) {
|
||||
key = key.toString().toLowerCase();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ public abstract class CustomKeyMap<K, V> extends MapWrapper<K, V> {
|
||||
* 构造<br>
|
||||
* 通过传入一个Map从而确定Map的类型,子类需创建一个空的Map,而非传入一个已有Map,否则值可能会被修改
|
||||
*
|
||||
* @param m Map 被包装的Map
|
||||
* @param emptyMap Map 被包装的Map,必须为空Map,否则自定义key会无效
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public CustomKeyMap(Map<K, V> m) {
|
||||
super(m);
|
||||
public CustomKeyMap(Map<K, V> emptyMap) {
|
||||
super(emptyMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3,7 +3,8 @@ package cn.hutool.core.map;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* 固定大小的{@link LinkedHashMap} 实现
|
||||
* 固定大小的{@link LinkedHashMap} 实现<br>
|
||||
* 注意此类非线程安全,由于{@link #get(Object)}操作会修改链表的顺序结构,因此也不可以使用读写锁。
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
|
@ -19,13 +19,14 @@ public class FuncKeyMap<K, V> extends CustomKeyMap<K, V> {
|
||||
// ------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* 构造<br>
|
||||
* 注意提供的Map中不能有键值对,否则可能导致自定义key失效
|
||||
*
|
||||
* @param m Map
|
||||
* @param emptyMap Map,提供的空map
|
||||
* @param keyFunc 自定义KEY的函数
|
||||
*/
|
||||
public FuncKeyMap(Map<K, V> m, Function<Object, K> keyFunc) {
|
||||
super(m);
|
||||
public FuncKeyMap(Map<K, V> emptyMap, Function<Object, K> keyFunc) {
|
||||
super(emptyMap);
|
||||
this.keyFunc = keyFunc;
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
|
@ -13,7 +13,7 @@ import java.util.function.Supplier;
|
||||
* @param <V> Value类型
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public class MapBuilder<K, V> implements Builder<Map<K,V>> {
|
||||
public class MapBuilder<K, V> implements Builder<Map<K, V>> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Map<K, V> map;
|
||||
@ -120,6 +120,17 @@ public class MapBuilder<K, V> implements Builder<Map<K,V>> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空Map
|
||||
*
|
||||
* @return this
|
||||
* @since 5.7.23
|
||||
*/
|
||||
public MapBuilder<K, V> clear() {
|
||||
this.map.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建后的map
|
||||
*
|
||||
|
@ -88,7 +88,6 @@ public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, S
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
raw.putAll(m);
|
||||
}
|
||||
@ -99,25 +98,21 @@ public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, S
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public Collection<V> values() {
|
||||
return raw.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public Set<K> keySet() {
|
||||
return raw.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
return raw.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public Iterator<Entry<K, V>> iterator() {
|
||||
return this.entrySet().iterator();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
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() {
|
||||
final Set<Map.Entry<K, V>> hashSet = new LinkedHashSet<>();
|
||||
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;
|
||||
}
|
||||
@ -191,7 +190,7 @@ public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Ser
|
||||
|
||||
@Override
|
||||
public Map.Entry<K, V> next() {
|
||||
return new Entry<>(keysIter.next(), valuesIter.next());
|
||||
return new SimpleEntry<>(keysIter.next(), valuesIter.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -209,48 +208,4 @@ public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Ser
|
||||
", 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
236
hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java
Normal file
236
hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
259
hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java
Normal file
259
hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java
Normal 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();
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 列表类型值的Map实现
|
||||
* 多参数类型的Map实现,包括集合类型值的Map和Table
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.core.map.multi;
|
||||
package cn.hutool.core.map.multi;
|
||||
|
@ -8,7 +8,8 @@ import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 可复用的字符串生成器,非线程安全
|
||||
* 可复用的字符串生成器,非线程安全<br>
|
||||
* TODO 6.x移除此类,java8的StringBuilder非常完善了,无需重写。
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.0.0
|
||||
|
@ -20,35 +20,60 @@ public class ModifierUtil {
|
||||
* @since 4.0.5
|
||||
*/
|
||||
public enum ModifierType {
|
||||
/** public修饰符,所有类都能访问 */
|
||||
/**
|
||||
* public修饰符,所有类都能访问
|
||||
*/
|
||||
PUBLIC(Modifier.PUBLIC),
|
||||
/** private修饰符,只能被自己访问和修改 */
|
||||
/**
|
||||
* private修饰符,只能被自己访问和修改
|
||||
*/
|
||||
PRIVATE(Modifier.PRIVATE),
|
||||
/** protected修饰符,自身、子类及同一个包中类可以访问 */
|
||||
/**
|
||||
* protected修饰符,自身、子类及同一个包中类可以访问
|
||||
*/
|
||||
PROTECTED(Modifier.PROTECTED),
|
||||
/** static修饰符,(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类 */
|
||||
/**
|
||||
* static修饰符,(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类
|
||||
*/
|
||||
STATIC(Modifier.STATIC),
|
||||
/** final修饰符,最终修饰符,指定此变量的值不能变,使用在方法上表示不能被重载 */
|
||||
/**
|
||||
* final修饰符,最终修饰符,指定此变量的值不能变,使用在方法上表示不能被重载
|
||||
*/
|
||||
FINAL(Modifier.FINAL),
|
||||
/** synchronized,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。 */
|
||||
/**
|
||||
* synchronized,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。
|
||||
*/
|
||||
SYNCHRONIZED(Modifier.SYNCHRONIZED),
|
||||
/** (易失修饰符)指定该变量可以同时被几个线程控制和修改 */
|
||||
/**
|
||||
* (易失修饰符)指定该变量可以同时被几个线程控制和修改
|
||||
*/
|
||||
VOLATILE(Modifier.VOLATILE),
|
||||
/** (过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量,序列化时忽略 */
|
||||
/**
|
||||
* (过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量,序列化时忽略
|
||||
*/
|
||||
TRANSIENT(Modifier.TRANSIENT),
|
||||
/** native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。 */
|
||||
/**
|
||||
* native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。
|
||||
*/
|
||||
NATIVE(Modifier.NATIVE),
|
||||
|
||||
/** abstract,将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现。 */
|
||||
/**
|
||||
* abstract,将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现。
|
||||
*/
|
||||
ABSTRACT(Modifier.ABSTRACT),
|
||||
/** strictfp,一旦使用了关键字strictfp来声明某个类、接口或者方法时,那么在这个关键字所声明的范围内所有浮点运算都是精确的,符合IEEE-754规范的。 */
|
||||
/**
|
||||
* strictfp,一旦使用了关键字strictfp来声明某个类、接口或者方法时,那么在这个关键字所声明的范围内所有浮点运算都是精确的,符合IEEE-754规范的。
|
||||
*/
|
||||
STRICT(Modifier.STRICT);
|
||||
|
||||
/** 修饰符枚举对应的int修饰符值 */
|
||||
/**
|
||||
* 修饰符枚举对应的int修饰符值
|
||||
*/
|
||||
private final int value;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param modifier 修饰符int表示,见{@link Modifier}
|
||||
*/
|
||||
ModifierType(int modifier) {
|
||||
@ -57,6 +82,7 @@ public class ModifierUtil {
|
||||
|
||||
/**
|
||||
* 获取修饰符枚举对应的int修饰符值,值见{@link Modifier}
|
||||
*
|
||||
* @return 修饰符枚举对应的int修饰符值
|
||||
*/
|
||||
public int getValue() {
|
||||
@ -67,7 +93,7 @@ public class ModifierUtil {
|
||||
/**
|
||||
* 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true)
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param clazz 类
|
||||
* @param modifierTypes 修饰符枚举
|
||||
* @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false
|
||||
*/
|
||||
@ -81,7 +107,7 @@ public class ModifierUtil {
|
||||
/**
|
||||
* 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true)
|
||||
*
|
||||
* @param constructor 构造方法
|
||||
* @param constructor 构造方法
|
||||
* @param modifierTypes 修饰符枚举
|
||||
* @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false
|
||||
*/
|
||||
@ -95,7 +121,7 @@ public class ModifierUtil {
|
||||
/**
|
||||
* 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true)
|
||||
*
|
||||
* @param method 方法
|
||||
* @param method 方法
|
||||
* @param modifierTypes 修饰符枚举
|
||||
* @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false
|
||||
*/
|
||||
@ -109,7 +135,7 @@ public class ModifierUtil {
|
||||
/**
|
||||
* 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true)
|
||||
*
|
||||
* @param field 字段
|
||||
* @param field 字段
|
||||
* @param modifierTypes 修饰符枚举
|
||||
* @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false
|
||||
*/
|
||||
@ -226,15 +252,27 @@ public class ModifierUtil {
|
||||
return clazz.isSynthetic();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否抽象方法
|
||||
*
|
||||
* @param method 方法
|
||||
* @return 是否抽象方法
|
||||
* @since 5.7.23
|
||||
*/
|
||||
public static boolean isAbstract(Method method) {
|
||||
return hasModifier(method, ModifierType.ABSTRACT);
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 多个修饰符做“与”操作,表示同时存在多个修饰符
|
||||
*
|
||||
* @param modifierTypes 修饰符列表,元素不能为空
|
||||
* @return “与”之后的修饰符
|
||||
*/
|
||||
private static int modifiersToInt(ModifierType... modifierTypes) {
|
||||
int modifier = modifierTypes[0].getValue();
|
||||
for(int i = 1; i < modifierTypes.length; i++) {
|
||||
for (int i = 1; i < modifierTypes.length; i++) {
|
||||
modifier |= modifierTypes[i].getValue();
|
||||
}
|
||||
return modifier;
|
||||
|
@ -3,6 +3,7 @@ package cn.hutool.core.util;
|
||||
import cn.hutool.core.annotation.Alias;
|
||||
import cn.hutool.core.bean.NullWrapperBean;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.UniqueKeySet;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
@ -17,6 +18,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -347,11 +349,12 @@ public class ReflectUtil {
|
||||
/**
|
||||
* 是否为父类引用字段<br>
|
||||
* 当字段所在类是对象子类时(对象中定义的非static的class),会自动生成一个以"this$0"为名称的字段,指向父类对象
|
||||
*
|
||||
* @param field 字段
|
||||
* @return 是否为父类引用字段
|
||||
* @since 5.7.20
|
||||
*/
|
||||
public static boolean isOuterClassField(Field field){
|
||||
public static boolean isOuterClassField(Field field) {
|
||||
return "this$0".equals(field.getName());
|
||||
}
|
||||
|
||||
@ -648,51 +651,47 @@ public class ReflectUtil {
|
||||
*/
|
||||
public static Method[] getMethods(Class<?> beanClass) throws SecurityException {
|
||||
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 withSuperClassMethods 是否包括父类的方法列表
|
||||
* @param beanClass 类或接口
|
||||
* @param withSupers 是否包括父类或接口的方法列表
|
||||
* @param withMethodFromObject 是否包括Object中的方法
|
||||
* @return 方法列表
|
||||
* @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);
|
||||
|
||||
Method[] allMethods = null;
|
||||
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) {
|
||||
declaredMethods = searchType.getDeclaredMethods();
|
||||
if (null == allMethods) {
|
||||
allMethods = declaredMethods;
|
||||
} else {
|
||||
allMethods = ArrayUtil.append(allMethods, declaredMethods);
|
||||
}
|
||||
searchType = withSuperClassMethods ? searchType.getSuperclass() : null;
|
||||
}
|
||||
if (beanClass.isInterface()) {
|
||||
// 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法
|
||||
return withSupers ? beanClass.getMethods() : beanClass.getDeclaredMethods();
|
||||
}
|
||||
return allMethods;
|
||||
|
||||
final UniqueKeySet<String, Method> result = new UniqueKeySet<>(true, ReflectUtil::getUniqueKey);
|
||||
Class<?> searchType = beanClass;
|
||||
while (searchType != null) {
|
||||
if (false == withMethodFromObject && Object.class == searchType) {
|
||||
break;
|
||||
}
|
||||
result.addAllIfAbsent(Arrays.asList(searchType.getDeclaredMethods()));
|
||||
result.addAllIfAbsent(getDefaultMethodsFromInterface(searchType));
|
||||
|
||||
|
||||
searchType = (withSupers && false == searchType.isInterface()) ? searchType.getSuperclass() : null;
|
||||
}
|
||||
|
||||
return result.toArray(new Method[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -788,10 +787,10 @@ public class ReflectUtil {
|
||||
|
||||
String name = method.getName();
|
||||
// 跳过getClass这个特殊方法
|
||||
if("getClass".equals(name)){
|
||||
if ("getClass".equals(name)) {
|
||||
return false;
|
||||
}
|
||||
if(ignoreCase){
|
||||
if (ignoreCase) {
|
||||
name = name.toLowerCase();
|
||||
}
|
||||
switch (parameterCount) {
|
||||
@ -1055,4 +1054,47 @@ public class ReflectUtil {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import cn.hutool.core.bean.copier.CopyOptions;
|
||||
import cn.hutool.core.bean.copier.ValueProvider;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.map.MapBuilder;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
@ -78,14 +79,15 @@ public class BeanUtilTest {
|
||||
|
||||
@Test
|
||||
public void fillBeanWithMapIgnoreCaseTest() {
|
||||
HashMap<String, Object> map = MapUtil.newHashMap();
|
||||
map.put("Name", "Joe");
|
||||
map.put("aGe", 12);
|
||||
map.put("openId", "DFDFSDFWERWER");
|
||||
Map<String, Object> map = MapBuilder.<String, Object>create()
|
||||
.put("Name", "Joe")
|
||||
.put("aGe", 12)
|
||||
.put("openId", "DFDFSDFWERWER")
|
||||
.build();
|
||||
SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false);
|
||||
Assert.assertEquals(person.getName(), "Joe");
|
||||
Assert.assertEquals(person.getAge(), 12);
|
||||
Assert.assertEquals(person.getOpenid(), "DFDFSDFWERWER");
|
||||
Assert.assertEquals("Joe", person.getName());
|
||||
Assert.assertEquals(12, person.getAge());
|
||||
Assert.assertEquals("DFDFSDFWERWER", person.getOpenid());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicIntegerArray;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
package cn.hutool.core.lang.test.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author 质量过关
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
public class ExamInfoDict implements Serializable {
|
||||
private static final long serialVersionUID = 3640936499125004525L;
|
||||
|
||||
|
||||
// 主键
|
||||
private Integer id; // 可当作题号
|
||||
// 试题类型 客观题 0主观题 1
|
||||
@ -18,49 +20,7 @@ public class ExamInfoDict implements Serializable {
|
||||
// 试题是否作答
|
||||
private Integer answerIs;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
public Integer getId(Integer defaultValue) {
|
||||
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 + '}';
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ public class ReflectUtilTest {
|
||||
@Test
|
||||
public void getMethodsTest() {
|
||||
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()));
|
||||
@ -35,7 +35,7 @@ public class ReflectUtilTest {
|
||||
//null过滤器测试
|
||||
methods = ReflectUtil.getMethods(ExamInfoDict.class, null);
|
||||
|
||||
Assert.assertEquals(22, methods.length);
|
||||
Assert.assertEquals(20, methods.length);
|
||||
final Method method2 = methods[0];
|
||||
Assert.assertNotNull(method2);
|
||||
}
|
||||
@ -114,9 +114,9 @@ public class ReflectUtilTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void getMethodBenchTest(){
|
||||
public void getMethodBenchTest() {
|
||||
// 预热
|
||||
getMethod(TestBenchClass.class, false, "getH");
|
||||
getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH");
|
||||
|
||||
final TimeInterval timer = DateUtil.timer();
|
||||
timer.start();
|
||||
@ -127,7 +127,7 @@ public class ReflectUtilTest {
|
||||
|
||||
timer.restart();
|
||||
for (int i = 0; i < 100000000; i++) {
|
||||
getMethod(TestBenchClass.class, false, "getH");
|
||||
getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH");
|
||||
}
|
||||
Console.log(timer.interval());
|
||||
}
|
||||
@ -150,7 +150,7 @@ public class ReflectUtilTest {
|
||||
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)) {
|
||||
return null;
|
||||
}
|
||||
@ -169,4 +169,74 @@ public class ReflectUtilTest {
|
||||
}
|
||||
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方法
|
||||
// 因此此处得到包括TestInterface1、TestInterface2、TestInterface3中一共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() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* XXTEA(Corrected 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
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* TEA(Tiny 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);
|
||||
}
|
||||
}
|
@ -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 fileName The file or resource name 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</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} to be thrown.
|
||||
*/
|
||||
public ScriptRuntimeException(String message, String fileName, int lineNumber) {
|
||||
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 fileName The filename
|
||||
|
Loading…
x
Reference in New Issue
Block a user