一、HashSet
HashSet是一个基于哈希表(HashMap)实现,元素不可重复的集合,通过这些特点可以快速找出重复的元素。
HashMap中当元素的hash值相同时,桶位置索引的计算值就会相同,且计算值比较"=="和equals结果为true,认为key相等。通过重写hashCode与equals方法使得只根据类的某些属性值进行比较,从而判断两个元素是否“重复”。
二、实现
类
重写hashcode和equals方法
public class Bean<T, K> {
/**
* 需要比较的元素
*/
private final T data;
/**
* get方法
*/
private final Function<T, K>[] getProperty;
public Bean(T t, Function<T, K>[] getProperty) {
this.data = t;
this.getProperty = getProperty;
}
public T getData() {
return data;
}
@Override
public int hashCode() {
if (ArrayUtil.isEmpty(getProperty)) {
return data.hashCode();
}
// 参考lombok
int result = 1;
for (Function<T, K> get : getProperty) {
K property = get.apply(data);
result = result * 59 + (property == null ? 43 : property.hashCode());
}
return result;
}
@Override
public boolean equals(Object o) {
if (ArrayUtil.isEmpty(getProperty)) {
return data.equals(((Bean<T, K>) o).data);
}
boolean result = Boolean.TRUE;
for (Function<T, K> get : getProperty) {
K property = get.apply(data);
K property2 = get.apply(((Bean<T, K>) o).data);
result = result && property.equals(property2);
}
return result;
}
}
方法
利用HashSet找到“重复”元素
@SafeVarargs
public static <T, K> List<T> findDuplicateElements(List<T> list1, List<T> list2, Function<T, K>... getProperty) {
Assert.notEmpty(list1, "list is empty");
Assert.notEmpty(list2, "list is empty");
Set<Bean<T, K>> beanSet = list1.stream().map(a -> new Bean<>(a, getProperty)).collect(Collectors.toSet());
Set<T> duplicateSet = new HashSet<>();
for (T element : list2) {
if (beanSet.contains(new Bean<>(element, getProperty))) {
duplicateSet.add(element);
}
}
return new ArrayList<>(duplicateSet);
}
测试
例子
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserRole {
String roleName;
Integer level;
String fiveElements;
}
public static void main(String[] args) {
//测试一
List<UserRole> list1 = new ArrayList<>();
list1.add(new UserRole("骑士", 12, "earth"));
list1.add(new UserRole("骑士", 12, "earth"));
list1.add(new UserRole("牧师", 12, "wood"));
list1.add(new UserRole("法师", 13, "fire"));
List<UserRole> list2 = new ArrayList<>();
list2.add(new UserRole("骑士", 12, "fire"));
list2.add(new UserRole("法师", 15, "fire"));
System.out.println("查找两个集合中职业相同的UserRole");
System.out.println(findDuplicateElements(list1, list2, UserRole::getRoleName));
System.out.println("查找两个集合中五行属性相同、等级相同的UserRole");
System.out.println(findDuplicateElements(list1, list2, UserRole::getRoleName, UserRole::getLevel));
}
输出如下
三、工具类
import cn.hutool.core.util.ArrayUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.util.Assert;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class BeanComparatorUtil {
@SafeVarargs
public static <T, K> Boolean equalsByProperty(T o1, T o2, Function<T, K>... getProperty) {
return CollectionUtils.isNotEmpty(findDuplicateElements(Collections.singletonList(o1), Collections.singletonList(o2), getProperty));
}
/**
* 查找两个集合中属性相同的重复元素
*/
@SafeVarargs
public static <T, K> List<T> findDuplicateElements(List<T> list1, List<T> list2, Function<T, K>... getProperty) {
Assert.notEmpty(list1, "list is empty");
Assert.notEmpty(list2, "list is empty");
Set<Bean<T, K>> beanSet = max(list1, list2).stream().map(a -> new Bean<>(a, getProperty)).collect(Collectors.toSet());
Set<T> duplicateSet = new HashSet<>();
for (T element : min(list1, list2)) {
if (beanSet.contains(new Bean<>(element, getProperty))) {
duplicateSet.add(element);
}
}
return new ArrayList<>(duplicateSet);
}
/**
* 查找两个集合中属性相同的不重复元素
*/
@SafeVarargs
public static <T, K> List<T> findUnDuplicateElements(List<T> list1, List<T> list2, Function<T, K>... getProperty) {
Assert.notEmpty(list1, "list is empty");
Assert.notEmpty(list2, "list is empty");
return Stream.of(list1, list2).flatMap(Collection::stream)
.map(a -> new Bean<>(a, getProperty)).collect(Collectors.toSet())
.stream().map(Bean::getData).collect(Collectors.toList());
}
/**
* 只返回sourceList中不相同的元素
*/
@SafeVarargs
public static <T, K> List<T> findUnDuplicateElementsFromSourceList(List<T> sourceList, List<T> compareList, Function<T, K>... getProperty) {
Assert.notEmpty(sourceList, "list is empty");
Assert.notEmpty(compareList, "list is empty");
Set<Bean<T, K>> beanSet = compareList.stream().map(a -> new Bean<>(a, getProperty)).collect(Collectors.toSet());
return sourceList.stream().map(a -> new Bean<>(a, getProperty)).collect(Collectors.toSet())
.stream().filter(bean -> !beanSet.contains(bean)).map(Bean::getData).collect(Collectors.toList());
}
public static <T> List<T> min(List<T> list1, List<T> list2){
Comparator<List<T>> comparator = Comparator.comparingInt(List::size);
return BinaryOperator.minBy(comparator).apply(list1, list2);
}
public static <T> List<T> max(List<T> list1, List<T> list2){
Comparator<List<T>> comparator = Comparator.comparingInt(List::size);
return BinaryOperator.maxBy(comparator).apply(list1, list2);
}
private static class Bean<T, K> {
/**
* 需要比较的元素
*/
private final T data;
/**
* get方法
*/
private final Function<T, K>[] getProperty;
public Bean(T t, Function<T, K>[] getProperty) {
this.data = t;
this.getProperty = getProperty;
}
public T getData() {
return data;
}
@Override
public int hashCode() {
if (ArrayUtil.isEmpty(getProperty)) {
return data.hashCode();
}
// 参考lombok
int result = 1;
for (Function<T, K> get : getProperty) {
K property = get.apply(data);
result = result * 59 + (property == null ? 43 : property.hashCode());
}
return result;
}
@Override
public boolean equals(Object o) {
if (ArrayUtil.isEmpty(getProperty)) {
return data.equals(((Bean<T, K>) o).data);
}
boolean result = Boolean.TRUE;
for (Function<T, K> get : getProperty) {
K property = get.apply(data);
K property2 = get.apply(((Bean<T, K>) o).data);
result = result && property.equals(property2);
}
return result;
}
}
}
四、注意点
例子
public static void main(String[] args) {
List<Object> list1 = Arrays.asList(1,9,"2",1.23,new UserRole("骑士", 12, "earth"));
List<Object> list2 = Arrays.asList(1,10,"5",1.31,1.23,new UserRole("骑士", 12, "earth"));
System.out.println("查找两个集合中相同的元素");
System.out.println(findDuplicateElements(list1, list2));
}
输出如下
将实体类UserRole的@Data改成@Setter、@Getter
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserRole {
String roleName;
Integer level;
String fiveElements;
}
重新执行测试用例,输出如下:
可以看到结果中不包含UserRole了,原因是lombok的@Data会重写hashcode和equals方法,实体类是否“”重复“”取决于是否重写了hashCode和equals方法。