CodeGym /课程 /JAVA 25 SELF /集合的 union、intersection、difference 操作

集合的 union、intersection、difference 操作

JAVA 25 SELF
第 32 级 , 课程 2
可用

1. 操作 union(并集)

在 Java 中,用于表示集合的是接口 Set<T>。与列表(List)不同,集合保证元素唯一,通常也不关心顺序(除非使用特殊实现,例如 LinkedHashSet)。最常用的实现——HashSetTreeSet。它们的核心目标是快速判断元素是否存在,并确保没有重复。

什么时候需要使用集合?

  • 当你需要唯一性:例如,网站所有唯一访客的列表。
  • 当你需要快速检查元素是否存在:HashSetcontains 通常是常数时间。
  • 当你要执行典型的集合运算:并集、交集、差集。

Union 指合并两个及以上的集合:结果包含两个来源集合中的所有元素(不重复)。

实践示例

假设有两个学生集合,分别参加社团“机器人技术”和“编程”:

Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");

我们需要得到至少参加一个社团的所有学生的集合。

通过 Stream API 的解法

最直接的方式是把两个流合并,并收集到 Set

Set<String> all = Stream.concat(
        robotics.stream(),
        programming.stream()
    ).collect(Collectors.toSet());

System.out.println(all); // [Anya, Boris, Vika, Gleb, Dasha]

说明:

  • Stream.concat 合并两个流。
  • collect(Collectors.toSet()) 将元素收集为集合(自动去重)。

替代方案:超过两个集合

如果有三个及以上社团,使用 Stream.offlatMap

Set<String> math = Set.of("Zhenya", "Vika", "Boris");

Set<String> all = Stream.of(robotics, programming, math)
    .flatMap(Set::stream)
    .collect(Collectors.toSet());

System.out.println(all); // [Anya, Boris, Vika, Gleb, Dasha, Zhenya]

为什么用 Set?
因为 Set 会自动去重。如果收集到 List,同样的名字会出现多次。

2. 操作 intersection(交集)

Intersection 指同时存在于两个集合中的元素。

实践示例

找出同时参加“机器人技术”和“编程”的学生:

Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");

通过 Stream API 的解法:

Set<String> both = robotics.stream()
    .filter(programming::contains)
    .collect(Collectors.toSet());

System.out.println(both); // [Vika]

说明:
我们遍历“机器人技术”的成员,仅保留那些也在“编程”中的人。结果是一个包含两个社团共有名字的集合。

替代方式(不使用 Stream API)

可以使用内置方法 retainAll(会修改当前集合):

Set<String> intersection = new HashSet<>(robotics);
intersection.retainAll(programming);
System.out.println(intersection); // [Vika]

不过本讲聚焦 Stream API,因此强调流式处理。

3. 操作 difference(差集)

Difference 指第一个集合中存在、但第二个集合中不存在的元素。

实践示例

找出只参加“机器人技术”而不参加“编程”的学生:

Set<String> robotics = Set.of("Anya", "Boris", "Vika");
Set<String> programming = Set.of("Vika", "Gleb", "Dasha");

通过 Stream API 的解法:

Set<String> onlyRobotics = robotics.stream()
    .filter(name -> !programming.contains(name))
    .collect(Collectors.toSet());

System.out.println(onlyRobotics); // [Anya, Boris]

说明:
我们过滤“机器人技术”的成员,只保留那些不在“编程”中的人。

替代方式(不使用 Stream API)

Set<String> difference = new HashSet<>(robotics);
difference.removeAll(programming);
System.out.println(difference); // [Anya, Boris]

4. 实践任务:处理用户列表

任务 1:找出只参加一个社团的学生

需要知道谁只参加“机器人技术”或只参加“编程”,但不是同时参加。这就是对称差(xor 为集合的异或):

Set<String> onlyOne = Stream.concat(
        robotics.stream().filter(name -> !programming.contains(name)),
        programming.stream().filter(name -> !robotics.contains(name))
    ).collect(Collectors.toSet());

System.out.println(onlyOne); // [Anya, Boris, Gleb, Dasha]

任务 2:来自多个社团的所有唯一学生列表

Set<String> all = Stream.of(robotics, programming, math)
    .flatMap(Set::stream)
    .collect(Collectors.toSet());

System.out.println(all); // [Anya, Boris, Vika, Gleb, Dasha, Zhenya]

任务 3:找出不参加任何社团的学生

假设我们有全班所有学生名单:

Set<String> allStudents = Set.of("Anya", "Boris", "Vika", "Gleb", "Dasha", "Zhenya", "Igor", "Katya");

需要找出谁没有参加任何社团:

Set<String> attendees = Stream.of(robotics, programming, math)
    .flatMap(Set::stream)
    .collect(Collectors.toSet());

Set<String> notInAny = allStudents.stream()
    .filter(name -> !attendees.contains(name))
    .collect(Collectors.toSet());

System.out.println(notInAny); // [Igor, Katya]

5. 重要注意事项:equals、hashCode 与性能

为什么正确实现 equals 和 hashCode 很重要?

所有与集合(Set)相关的操作都依赖 equalshashCode 的正确性。如果你存放的是自定义类的对象(例如 Student),就必须重写这些方法,否则比较将不正确。

示例:

class Student {
    String name;
    int age;

    // 别忘了重写 equals 和 hashCode!
}

如果不这样做,即使两个学生的名字和年龄相同,对 Set 来说它们也会被视为不同的对象。

为什么优先使用 Set 而不是 List?

  • Setcontains 操作很快(通常是常数时间)。
  • List 中查找元素是线性时间,这在大集合时可能成为瓶颈。
  • 对于集合运算(unionintersectiondifference),Set 更高效也更符合语义。

6. 进行集合运算时的常见错误

错误 1:用 List 代替 Set 来做集合运算。 如果你把元素收集到 List,则不会去重,而且 contains 操作很慢。对于 union/intersection/difference,请使用 Set

错误 2:对象未实现 equals/hashCode。 如果你在 Set 中存放自定义类的对象,却没有重写 equalshashCode,那么交集与差集会出现“奇怪”的结果——语义上相同的对象不会被视为相等。

错误 3:在流处理中修改集合。 如果你在流中直接修改原始 Set(例如添加或删除元素),会抛出 ConcurrentModificationException。请始终对新集合进行操作。

错误 4:不易察觉的顺序丢失。 HashSet 不保证元素的顺序。如果顺序很重要——请使用 LinkedHashSetTreeSet

错误 5:对两个以上的集合还用 Stream.concat。 Stream.concat 只能合并两个流。对于更多集合,请使用 Stream.of(...)flatMap

错误 6:关于 null 的问题。 集合通常不喜欢 null 值,尤其当你使用 Set.of(...) 时——它不允许 null。若需要处理 null,请使用其他实现,或提前过滤。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION