Java自定义对象去重问题:HashSet与stream.distinct()失效求助
自定义对象/数组去重解决方案
问题根源
你遇到的核心问题是:HashSet和Stream的distinct()方法都依赖equals()与hashCode()的契约一致性——两个通过equals()判断相等的对象,必须返回相同的hashCode值。你只重写了PointType的equals(),但未重写hashCode(),导致JVM认为p1和p2(x、y相同的不同对象)是不同元素,因此去重失效。
1. 修复PointType类:实现完整的equals()和hashCode()
重写hashCode(),基于x、y属性生成哈希值,保证与equals()逻辑一致。同时优化equals()的严谨性(替换==为Double.compare(),避免浮点精度问题):
import java.util.Objects; public class PointType { private double x; private double y; public PointType(double x, double y) { this.x = x; this.y = y; } @Override public boolean equals(Object other) { if (this == other) return true; if (!(other instanceof PointType)) return false; PointType point = (PointType) other; return Double.compare(point.x, x) == 0 && Double.compare(point.y, y) == 0; } @Override public int hashCode() { // 基于x、y生成哈希值,Objects.hash会自动处理基本类型的包装 return Objects.hash(x, y); } // 可选:添加getter方便后续操作 public double getX() { return x; } public double getY() { return y; } }
修改后,原测试用例中的setB.size() == 2和listB.size() == 2断言都会通过:
HashSet会根据hashCode()分组,再用equals()去重stream().distinct()的逻辑与HashSet一致,自然生效
2. double[]数组的去重方案
针对double[](每个数组代表[x,y]坐标),由于数组的equals()是引用比较,无法直接用HashSet,提供三种优雅实现:
方案A:包装为自定义类(推荐)
和PointType思路一致,将double[]包装成类,重写equals()和hashCode(),复用上面的PointType即可,或者直接用List<Double>作为键(但性能略差)。
方案B:用TreeSet+数组比较器(自动去重,会排序)
TreeSet依赖比较逻辑而非哈希值,只要比较器认为两个数组相等,就会去重:
import java.util.Arrays; import java.util.Set; import java.util.TreeSet; // 示例:去重double[]数组 Set<double[]> uniquePoints = new TreeSet<>(Arrays::compare); uniquePoints.add(new double[]{1.0, 2.0}); uniquePoints.add(new double[]{1.0, 2.0}); uniquePoints.add(new double[]{2.0, 2.0}); System.out.println(uniquePoints.size()); // 输出2
转成List的写法:
import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; List<double[]> originalList = Arrays.asList( new double[]{1.0,2.0}, new double[]{1.0,2.0}, new double[]{2.0,2.0}, new double[]{2.0,2.0} ); List<double[]> uniqueList = originalList.stream() .collect(Collectors.toCollection(() -> new TreeSet<>(Arrays::compare))) .stream() .collect(Collectors.toList());
方案C:Stream+自定义Predicate(保留原顺序)
如果需要保持原列表的顺序,用Predicate记录已出现的元素:
import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; List<double[]> originalList = new ArrayList<>(Arrays.asList( new double[]{1.0,2.0}, new double[]{2.0,2.0}, new double[]{1.0,2.0}, new double[]{2.0,2.0} )); List<double[]> uniqueList = originalList.stream() .filter(new Predicate<double[]>() { private final Set<List<Double>> seen = new HashSet<>(); @Override public boolean test(double[] arr) { return seen.add(Arrays.asList(arr[0], arr[1])); } }) .collect(Collectors.toList()); // 输出顺序:[1.0,2.0], [2.0,2.0]
3. 额外优化:保持顺序的对象去重
如果需要保留原列表的顺序(HashSet和TreeSet会打乱顺序),同样可以用Predicate的方式:
List<PointType> uniqueOrderedList = listB.stream() .filter(new Predicate<PointType>() { private final Set<PointType> seen = new HashSet<>(); @Override public boolean test(PointType p) { return seen.add(p); } }) .collect(Collectors.toList());
内容的提问来源于stack exchange,提问作者Gian-Andrea Heinrich
相关产品推荐
相关产品推荐

