diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3fd0cf5e6..dfefa113b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
-# 5.7.23 (2022-03-08)
+# 5.7.23 (2022-03-09)
### 🐣新特性
* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee)
@@ -10,6 +10,7 @@
* 【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)
diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java b/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java
new file mode 100644
index 000000000..f4fbcb15c
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java
@@ -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
+ * 通过自定义唯一键,通过{@link #uniqueGenerator}生成节点对象对应的键作为Map的key,确定唯一
+ * 此Set与HashSet不同的是,HashSet依赖于{@link Object#equals(Object)}确定唯一
+ * 但是很多时候我们无法对对象进行修改,此时在外部定义一个唯一规则,即可完成去重。
+ *
+ * {@code Set set = new UniqueKeySet<>(UniqueTestBean::getId);}
+ *
+ *
+ * @param 唯一键类型
+ * @param 值对象
+ * @author looly
+ * @since 5.7.23
+ */
+public class UniqueKeySet extends AbstractSet implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private Map map;
+ private final Function uniqueGenerator;
+
+ //region 构造
+
+ /**
+ * 构造
+ *
+ * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键
+ */
+ public UniqueKeySet(Function uniqueGenerator) {
+ this(false, uniqueGenerator);
+ }
+
+ /**
+ * 构造
+ *
+ * @param isLinked 是否保持加入顺序
+ * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键
+ */
+ public UniqueKeySet(boolean isLinked, Function uniqueGenerator) {
+ this(MapBuilder.create(isLinked), uniqueGenerator);
+ }
+
+ /**
+ * 构造
+ *
+ * @param initialCapacity 初始容量
+ * @param loadFactor 增长因子
+ * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键
+ */
+ public UniqueKeySet(int initialCapacity, float loadFactor, Function uniqueGenerator) {
+ this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor)), uniqueGenerator);
+ }
+
+ /**
+ * 构造
+ *
+ * @param builder 初始Map,定义了Map类型
+ * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键
+ */
+ public UniqueKeySet(MapBuilder builder, Function uniqueGenerator) {
+ this.map = builder.build();
+ this.uniqueGenerator = uniqueGenerator;
+ }
+ //endregion
+
+ @Override
+ public Iterator 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 clone() {
+ try {
+ UniqueKeySet newSet = (UniqueKeySet) super.clone();
+ newSet.map = ObjectUtil.clone(this.map);
+ return newSet;
+ } catch (CloneNotSupportedException e) {
+ throw new InternalError(e);
+ }
+ }
+
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java
index 4e4a78faf..7b746bc6e 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java
@@ -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 {
/**
* 是否为父类引用字段
* 当字段所在类是对象子类时(对象中定义的非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,40 +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));
}
/**
* 获得一个类中所有方法列表,直接反射获取,无缓存
- * 接口获取方法和默认方法
+ * 接口获取方法和默认方法,获取的方法包括:
+ *
+ * - 本类中的所有方法(包括static方法)
+ * - 父类中的所有方法(包括static方法)
+ * - Object中(包括static方法)
+ *
*
- * @param beanClass 类或接口
- * @param withSupers 是否包括父类或接口的方法列表
+ * @param beanClass 类或接口
+ * @param withSupers 是否包括父类或接口的方法列表
+ * @param withMethodFromObject 是否包括Object中的方法
* @return 方法列表
* @throws SecurityException 安全检查异常
*/
- public static Method[] getMethodsDirectly(Class> beanClass, boolean withSupers) throws SecurityException {
+ public static Method[] getMethodsDirectly(Class> beanClass, boolean withSupers, boolean withMethodFromObject) throws SecurityException {
Assert.notNull(beanClass);
- if(beanClass.isInterface()){
+ if (beanClass.isInterface()) {
// 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法
return withSupers ? beanClass.getMethods() : beanClass.getDeclaredMethods();
}
- Method[] allMethods = null;
+ final UniqueKeySet result = new UniqueKeySet<>(true, ReflectUtil::getUniqueKey);
Class> searchType = beanClass;
- Method[] declaredMethods;
- while (searchType != null && searchType != Object.class) {
- declaredMethods = searchType.getDeclaredMethods();
- if (null == allMethods) {
- allMethods = declaredMethods;
- } else {
- allMethods = ArrayUtil.append(allMethods, declaredMethods);
+ 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 ArrayUtil.append(allMethods);
+ return result.toArray(new Method[0]);
}
/**
@@ -777,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) {
@@ -1044,4 +1054,47 @@ public class ReflectUtil {
}
return accessibleObject;
}
+
+ /**
+ * 获取方法的唯一键,结构为:
+ *
+ * 返回类型#方法名:参数1类型,参数2类型...
+ *
+ *
+ * @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 getDefaultMethodsFromInterface(Class> clazz) {
+ List result = new ArrayList<>();
+ for (Class> ifc : clazz.getInterfaces()) {
+ for (Method m : ifc.getMethods()) {
+ if (false == ModifierUtil.isAbstract(m)) {
+ result.add(m);
+ }
+ }
+ }
+ return result;
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java
new file mode 100644
index 000000000..9dd45058f
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java
@@ -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 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;
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java
index bb9dca5f3..6affbabb6 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java
@@ -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,7 +114,7 @@ public class ReflectUtilTest {
@Test
@Ignore
- public void getMethodBenchTest(){
+ public void getMethodBenchTest() {
// 预热
getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH");
@@ -171,14 +171,26 @@ public class ReflectUtilTest {
}
@Test
- public void getMethodsFromClassExtends(){
+ public void getMethodsFromClassExtends() {
// 继承情况下,需解决方法去重问题
- final Method[] methods = ReflectUtil.getMethods(C2.class);
- Assert.assertEquals(2, methods.length);
+ 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(){
+ public void getMethodsFromInterfaceTest() {
// 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法
// 因此此处得到包括TestInterface1、TestInterface2、TestInterface3中一共4个方法
final Method[] methods = ReflectUtil.getMethods(TestInterface3.class);
@@ -189,25 +201,26 @@ public class ReflectUtilTest {
Assert.assertArrayEquals(methods, publicMethods);
}
- interface TestInterface1{
+ interface TestInterface1 {
void getA();
+
void getB();
- default void getC(){
+ default void getC() {
}
}
- interface TestInterface2 extends TestInterface1{
+ interface TestInterface2 extends TestInterface1 {
@Override
void getB();
}
- interface TestInterface3 extends TestInterface2{
+ interface TestInterface3 extends TestInterface2 {
void get3();
}
- class C1 implements TestInterface2{
+ class C1 implements TestInterface2 {
@Override
public void getA() {
@@ -220,7 +233,7 @@ public class ReflectUtilTest {
}
}
- class C2 extends C1{
+ class C2 extends C1 {
@Override
public void getA() {