当前位置:首页 > 后端开发 > 正文

java 怎么去重

va去重可用Set集合(自动 去重)、Stream.distinct()或遍历时检查是否存在实现,具体依场景选

Java编程中,去重是一个常见的需求,尤其是在处理集合数据时,以下是几种常用的方法及其详细实现方式:

使用Set特性自动去重

Java中的Set接口天生具备不允许重复元素的特性(基于equals()方法和哈希码判断唯一性),这是最简单直接的方式。
| 实现类型 | 特点 | 适用场景 |
|—————-|———————————————————————-|——————————|
| HashSet | 无序存储,性能高;不保证插入顺序 | 对顺序无要求的场景 |
| LinkedHashSet| 维护元素的插入顺序,遍历时按添加顺序输出 | 需要保留原始顺序的情况 |
| TreeSet | 自动排序(自然序或自定义比较器),有序集合 | 需要排序后的去重结果 |

示例代码:

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetDeduplication {
    public static void main(String[] args) {
        Integer[] numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5};
        // 普通去重(无序)
        Set<Integer> hashSet = new HashSet<>(List.of(numbers));
        System.out.println("HashSet结果: " + hashSet); // 输出类似 [1, 2, 3, 4, 5, 6, 9]
        // 保留顺序的去重
        Set<Integer> linkedHashSet = new LinkedHashSet<>(List.of(numbers));
        System.out.println("LinkedHashSet结果: " + linkedHashSet); // 按插入顺序输出 [3, 1, 4, 5, 9, 2, 6]
        // 排序后的去重
        Set<Integer> treeSet = new TreeSet<>(List.of(numbers));
        System.out.println("TreeSet结果: " + treeSet); // 升序排列 [1, 2, 3, 4, 5, 6, 9]
    }
}

️注意:如果对象未正确重写hashCode()equals()方法,可能导致错误的去重行为,例如自定义类作为元素时,必须覆盖这两个方法以确保逻辑一致性。


Stream API流式处理

Java 8引入的Stream API提供了声明式的数据处理能力,结合distinct()中间操作可实现优雅的去重。

基础用法(针对基本类型/不可变对象):

List<String> originalList = Arrays.asList("apple", "banana", "apple", "orange");
List<String> distinctList = originalList.stream().distinct().toList();
// distinctList → ["apple", "banana", "orange"]

复杂对象去重技巧:

当处理自定义对象时,需确保其正确实现了equals()方法。

java 怎么去重  第1张

record Person(String name, int age) {} // Java 16+记录类自动生成equals/hashCode
List<Person> people = List.of(
    new Person("Alice", 30),
    new Person("Bob", 25),
    new Person("Alice", 30) // 与第一个对象相等
);
List<Person> uniquePeople = people.stream()
    .distinct()
    .collect(Collectors.toList());
// uniquePeople大小变为2

分组统计扩展应用:

若需同时获取重复次数等信息,可配合Collectors.groupingBy使用:

Map<String, Long> frequencyMap = originalList.stream()
    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// {"apple":2, "banana":1, "orange":1}

手动迭代控制流程

对于特殊场景(如保持特定过滤逻辑),可以通过传统循环实现精细化控制,以下是两种典型模式:

方案1:双指针法(原地修改数组)推荐用于面试算法题

适用于已排序数组的原地去重,空间复杂度O(1):

public int removeDuplicates(int[] nums) {
    if (nums.length == 0) return 0;
    int slow = 0;
    for (int fast = 1; fast < nums.length; fast++) {
        if (nums[fast] != nums[slow]) {
            slow++;
            nums[slow] = nums[fast];
        }
    }
    return slow + 1; // 新长度
}

输入示例:[1,1,2,3,3,4] → 处理后前4个元素变为[1,2,3,4],返回值4。

方案2:辅助集合标记法(通用型解决方案)

适合任意类型的List结构:

public static <T> List<T> manualDeduplicate(List<T> list) {
    Set<T> seen = new HashSet<>();
    List<T> result = new ArrayList<>();
    for (T item : list) {
        if (!seen.contains(item)) {
            seen.add(item);
            result.add(item);
        }
    }
    return result;
}

此方法的优势在于:①可中途中断循环;②支持动态添加额外条件(如跳过某些特定值)。


数据库层面的优化策略

当数据源来自数据库时,应在SQL层面完成去重操作以提高效率,常见写法包括:
| 语法类型 | 示例 | 说明 |
|————————|————————————–|————————–|
| SELECT DISTINCT ... | SELECT DISTINCT department FROM employees; | 基础去重语句 |
| 窗口函数 | SELECT id, department, ROW_NUMBER() OVER (PARTITION BY department ORDER BY id) as rn FROM employees; | 配合WHERE子句过滤重复项 |
| GROUP BY聚合 | SELECT department, COUNT() FROM employees GROUP BY department; | 同时完成计数功能 |

特别注意:对于海量数据集,数据库索引可以显著提升DISTINCT操作的速度,建议为相关字段建立索引。


第三方库增强方案

某些场景下可以使用成熟工具库简化开发:

  1. Apache Commons CollectionsCollectionUtils提供便捷的集合操作工具类;
  2. Guava Multiset支持带计数的多重集合,适合需要知道元素出现频率的场景;
  3. Reactor Project的反应式编程模型可在异步流中实现实时去重。

FAQs

Q1:为什么用HashSet去重后元素的顺序改变了?

A: 因为HashSet基于哈希表实现,不保证存储顺序,若需维持插入顺序,应改用LinkedHashSet;若需要排序结果,则选择TreeSet,这三种Set的区别本质上是对内部数据结构的取舍:速度、顺序还是排序能力。

Q2:如何对包含null值的列表进行安全去重?

A: 标准集合类允许单个null元素存在(如HashSet),但多个null会被视为相同值,处理方法有两种:①预处理时过滤所有null值再进行去重;②使用特殊设计的Comparator显式处理null情况,示例代码如下:

List<String> withNulls = Arrays.asList("a", null, "b", null);
Set<String> safeSet = new LinkedHashSet<>(withNulls); // 只保留一个null
// safeSet → ["a", null,
0