mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
add UniqueKeySet
This commit is contained in:
parent
56d490e2ac
commit
8f0f3354e3
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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,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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得一个类中所有方法列表,直接反射获取,无缓存<br>
|
||||
* 接口获取方法和默认方法
|
||||
* 接口获取方法和默认方法,获取的方法包括:
|
||||
* <ul>
|
||||
* <li>本类中的所有方法(包括static方法)</li>
|
||||
* <li>父类中的所有方法(包括static方法)</li>
|
||||
* <li>Object中(包括static方法)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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<String, Method> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法的唯一键,结构为:
|
||||
* <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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user