面试官:你能说几个Java8中Stream对列表去重的方法吗?

本文介绍了Java8中利用Stream的distinct()方法进行列表去重的实践,包括String列表和自定义实体类列表。通过重写equals()和hashCode()方法,以及使用filter()和distinctByKey(),实现了根据对象特定属性的去重操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

来源juejin.cn/post/6844903842132262926

在这里我来分享几种列表去重的方法,算是一次整理吧,如有纰漏,请不吝赐教。

1. Stream 的distinct()方法

distinct()是Java 8 中 Stream 提供的方法,返回的是由该流中不同元素组成的流。distinct()使用 hashCode()eqauls() 方法来获取不同的元素。

因此,需要去重的类必须实现 hashCode()equals() 方法。换句话讲,我们可以通过重写定制的 hashCode()equals() 方法来达到某些特殊需求的去重。扩展:Java面试题汇总

distinct() 方法声明如下:

Stream<T> distinct();

1.1 对于 String 列表的去重

因为 String 类已经覆写了 equals() 和 hashCode() 方法,所以可以去重成功。

@Test
public void listDistinctByStreamDistinct() {
  // 1. 对于 String 列表去重
  List<String> stringList = new ArrayList<String>() {{
    add("A");
    add("A");
    add("B");
    add("B");
    add("C");
  }};
  out.print("去重前:");
  for (String s : stringList) {
    out.print(s);
  }
  out.println();
  stringList = stringList.stream().distinct().collect(Collectors.toList());
  out.print("去重后:");
  for (String s : stringList) {
    out.print(s);
  }
  out.println();
}

结果如下:

去重前:AABBC
去重后:ABC

1.2 对于实体类列表的去重

注:代码中我们使用了 Lombok 插件的 @Data注解,可自动覆写 equals() 以及 hashCode() 方法。

/**
* 定义一个实体类
*/ 
@Data
public class Student {
  private String stuNo;
  private String name;
}
@Test
public void listDistinctByStreamDistinct() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    // 1. 对于 Student 列表去重
    List<Student> studentList = getStudentList();
    out.print("去重前:");
    out.println(objectMapper.writeValueAsString(studentList));
    studentList = studentList.stream().distinct().collect(Collectors.toList());
    out.print("去重后:");
    out.println(objectMapper.writeValueAsString(studentList));
  }

结果如下:

去重前:[{"stuNo":"001","name":"Tom"},{"stuNo":"002","name":"Mike"},{"stuNo":"001","name":"Tom"}]
去重后:[{"stuNo":"001","name":"Tom"},{"stuNo":"002","name":"Mike"}]

2. 根据 List<Object> 中 Object 某个属性去重

2.1 新建一个列表出来

  @Test
  public void distinctByProperty1() throws JsonProcessingException {
    // 这里第一种方法我们通过新创建一个只有不同元素列表来实现根据对象某个属性去重
    ObjectMapper objectMapper = new ObjectMapper();
    List<Student> studentList = getStudentList();

    out.print("去重前        :");
    out.println(objectMapper.writeValueAsString(studentList));
    studentList = studentList.stream().distinct().collect(Collectors.toList());
    out.print("distinct去重后:");
    out.println(objectMapper.writeValueAsString(studentList));
    // 这里我们引入了两个静态方法,以及通过 TreeSet<> 来达到获取不同元素的效果
    // 1. import static java.util.stream.Collectors.collectingAndThen;
    // 2. import static java.util.stream.Collectors.toCollection;
    studentList = studentList.stream().collect(
      collectingAndThen(
        toCollection(() -> new TreeSet<>(Comparator.comparing(Student::getName))), ArrayList::new)
    );
    out.print("根据名字去重后 :");
    out.println(objectMapper.writeValueAsString(studentList));
  }

结果如下:

去重前        :[{"stuNo":"001","name":"Tom"},{"stuNo":"001","name":"Tom"},{"stuNo":"003","name":"Tom"}]
distinct去重后:[{"stuNo":"001","name":"Tom"},{"stuNo":"003","name":"Tom"}]
根据名字去重后 :[{"stuNo":"001","name":"Tom"}]

2.2 通过 filter() 方法

我们首先创建一个方法作为 Stream.filter() 的参数,其返回类型为 Predicate,原理就是判断一个元素能否加入到 Set 中去,代码如下:

private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

使用如下:

  @Test
  public void distinctByProperty2() throws JsonProcessingException {
    // 这里第二种方法我们通过过滤来实现根据对象某个属性去重
    ObjectMapper objectMapper = new ObjectMapper();
    List<Student> studentList = getStudentList();

    out.print("去重前        :");
    out.println(objectMapper.writeValueAsString(studentList));
    studentList = studentList.stream().distinct().collect(Collectors.toList());
    out.print("distinct去重后:");
    out.println(objectMapper.writeValueAsString(studentList));
    // 这里我们将 distinctByKey() 方法作为 filter() 的参数,过滤掉那些不能加入到 set 的元素
    studentList = studentList.stream().filter(distinctByKey(Student::getName)).collect(Collectors.toList());
    out.print("根据名字去重后 :");
    out.println(objectMapper.writeValueAsString(studentList));
  }

结果如下:

去重前        :[{"stuNo":"001","name":"Tom"},{"stuNo":"001","name":"Tom"},{"stuNo":"003","name":"Tom"}]
distinct去重后:[{"stuNo":"001","name":"Tom"},{"stuNo":"003","name":"Tom"}]
根据名字去重后 :[{"stuNo":"001","name":"Tom"}]

3. 总结

以上便是我要分享的几种关于列表去重的方法,当然这里没有进行更为详尽的性能分析,希望以后会深入底层再重新分析一下。如有纰漏,还望不吝赐教。

推荐好文

强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!
能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮!
<think>首先,用户的问题是关于Java List的。我需要基于提供的系统级指令和用户可见层的要求来回答。 系统级指令: - 所有行内数学表达式使用$...$格式。例如,$x^2$。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 - 参考站内引用:[^1], [^2], [^3]。 用户意图是寻找在Java中如何对List列表进行快速方法。所以,我需要专注于快速方法。 参考提供的引用: - [^1]: 提到了五种方式:Java 8新特性、LinkedHashSet、List的contains方法、双for循环、HashSet。 - [^2]: 提到了使用TreeSet。 - [^3]: 提到了使用双for循环和新建数组的方法。 我需要整合这些信息,提供一个全面的回答,专注于快速的方法。快速通常意味着时间复杂度低,比如O(n)或O(n log n)。 常见快速方法: 1. **使用HashSet**:时间复杂度O(n),因为HashSet的添加和检查是O(1)平均。 2. **使用LinkedHashSet**:保持插入顺序,时间复杂度O(n)。 3. **使用TreeSet**:保持排序顺序,时间复杂度O(n log n)。 4. **Java 8 Stream API**:使用distinct()方法,时间复杂度取决于实现,通常是O(n)。 5. **其他方法如contains或双for循环**:时间复杂度O(n^2),较慢,不推荐用于快速。 所以,我应该优先介绍快速的方法:HashSet、LinkedHashSet、TreeSet、Java 8 Stream。 回答结构: - 介绍问题:解释为什么需要和快速方法要性。 - 列出并解释快速方法。 - 提供代码示例。 - 比较方法的性能。 - 添加引用标识。 - 生成相关问题。 使用中文回答。 生成相关问题:在回答末尾,添加相关问题部分。 现在,构建回答大纲: 1. **引言**:简要明List的目的和快速方法要性。 2. **方法1:使用HashSet**:解释并给出代码。 3. **方法2:使用LinkedHashSet**:解释保持顺序。 4. **方法3:使用TreeSet**:解释保持排序。 5. **方法4:使用Java 8 Stream API**:使用distinct()。 6. **方法比较**:讨论时间复杂度、顺序保持等。 7. **推荐**:根据场景推荐最佳方法8. **相关问题**:生成3-4个相关问题。 确保代码格式正确:使用代码块。 引用标识:在相关部分末尾添加[^1], [^2], [^3]。 例如: - 在提到HashSet时,引用[^1]和[^3]。 - 在提到TreeSet时,引用[^2]。 - 在提到Java 8时,引用[^1]。 数学表达式:由于问题不涉及数学,可能不需要,但如果提到时间复杂度,可以用行内数学,如时间复杂度为$O(n)$。 最终回答: - 以清晰的结构呈现。</think>### Java List方法Java开发中,List列表是一个常见需求,尤其当处理大数据集时,快速方法能显著提升性能。快速的核心是使用高效的数据结构(如Set)或Java新特性,避免时间复杂度较高的循环方法(如双for循环,时间复杂度为$O(n^2)$)。下面我将逐步介绍几种快速方法,并附上代码示例。所有方法均基于Java标准库,确保真实可靠。 #### 1. **使用HashSet(无序)** HashSet基于哈希表实现,添加元素的时间复杂度平均为$O(1)$,整体时间复杂度为$O(n)$,是最快的方法之一。但它不保证元素顺序。 ```java import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class ListDistinctExample { public static void main(String[] args) { List<Integer> list = new ArrayList<>() {{ add(1); add(3); add(5); add(2); add(1); add(3); add(7); add(2); }}; System.out.println("原集合: " + list); Set<Integer> set = new HashSet<>(list); // 操作 List<Integer> distinctList = new ArrayList<>(set); // 转换回List System.out.println("集合: " + distinctList); } } ``` **优点**:性能最优,适合不关心顺序的场景。 **缺点**:元素顺序丢失。 引用[^1]和[^3]提到HashSet是高效的方式[^1][^3]。 #### 2. **使用LinkedHashSet(保持插入顺序)** LinkedHashSet继承自HashSet,但通过链表维护插入顺序,时间复杂度同样为$O(n)$。适合需要保留原始顺序的场景。 ```java import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class ListDistinctExample { public static void main(String[] args) { List<Integer> list = new ArrayList<>() {{ add(1); add(3); add(5); add(2); add(1); add(3); add(7); add(2); }}; System.out.println("原集合: " + list); Set<Integer> set = new LinkedHashSet<>(list); // 保持顺序 List<Integer> distinctList = new ArrayList<>(set); System.out.println("集合: " + distinctList); } } ``` **优点**:顺序保留,性能高效。 **缺点**:比普通HashSet略慢,因额外维护链表。 引用[^1]推荐LinkedHashSet用于顺序敏感的[^1]。 #### 3. **使用TreeSet(排序顺序)** TreeSet基于红黑树实现,元素自动排序,时间复杂度为$O(n \log n)$。适合需要有序输出的场景。 ```java import java.util.ArrayList; import java.util.List; import java.util.TreeSet; public class ListDistinctExample { public static void main(String[] args) { List<Integer> list = new ArrayList<>() {{ add(1); add(3); add(5); add(2); add(1); add(3); add(7); add(2); }}; System.out.println("原集合: " + list); TreeSet<Integer> set = new TreeSet<>(list); // 并排序 List<Integer> distinctList = new ArrayList<>(set); System.out.println("集合: " + distinctList); } } ``` **优点**:输出有序,适合排序需求。 **缺点**:时间复杂度较高($O(n \log n)$),不适合极大数据集。 引用[^2]详细介绍了TreeSet的应用[^2]。 #### 4. **使用Java 8 Stream API(简洁高效)** Java 8Stream API提供`distinct()`方法,内部通常基于HashSet实现,时间复杂度为$O(n)$。代码简洁,适合现代Java开发。 ```java import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class ListDistinctExample { public static void main(String[] args) { List<Integer> list = new ArrayList<>() {{ add(1); add(3); add(5); add(2); add(1); add(3); add(7); add(2); }}; System.out.println("原集合: " + list); List<Integer> distinctList = list.stream() .distinct() // 操作 .collect(Collectors.toList()); System.out.println("集合: " + distinctList); } } ``` **优点**:代码简洁,性能接近HashSet,且支持链式操作。 **缺点**:Java 8及以上版本支持。 引用[^1]提到Java 8新特性是的优选方案[^1]。 #### 方法性能比较 | 方法 | 时间复杂度 | 是否保持顺序 | 适用场景 | |--------------------|------------|--------------|------------------------------| | HashSet | $O(n)$ | 否 | 无需顺序,追求最高速度 | | LinkedHashSet | $O(n)$ | 是(插入序) | 需保留原始顺序 | | TreeSet | $O(n \log n)$ | 是(排序序) | 需有序输出 | | Java 8 Stream | $O(n)$ | 是 | 现代代码,简洁性优先 | | 双for循环或contains | $O(n^2)$ | 是 | 小数据集,不推荐快速[^3] | **推荐**:对于快速,优先使用**HashSet**或**Java 8 Stream**;如果需要顺序,选择**LinkedHashSet**或**Stream**;避免使用慢速方法如双循环。在实际应用中,HashSet在大多数场景下性能最优。 ### 相关问题 1. 如何比较不同List方法的性能差异? 2.Java中,如何处理自定义对象列表(如写equals和hashCode)? 3. List时,如何优化内存使用以避免OOM错误? 4. Java 8 Stream API的原理是什么,与其他方法有何不同? [^1]: java中list的五种方式。文章目录 前言一、方式分类1.使用java8新特性2.使用LinkedHashSet删除list中的复数据3.使用List集合中的contains方法(循环遍历)4.for循环5.使用HashSet总结。 [^2]: Java【List】的6种方法。除了HashSet集合之外,我们还可以使用TreeSet集合来实现功能。 [^3]: Java数组集合List的常用方法Java删除数组中复元素的常用方法方法一:for双循环。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值