Java中如何实现时间的A - B(集合论A\B)运算?
在Java中实现集合论的A-B(差集)操作
一、普通集合的差集实现
Java集合框架自带了多种实现集合差集的方式,针对不同场景可以灵活选择:
1. 使用removeAll方法(最直接)
Collection接口的removeAll方法会移除集合中所有存在于指定集合中的元素,正好对应集合论里A-B的逻辑。注意这个方法会修改原集合,所以如果不想破坏原始数据,一定要先复制一份集合再操作:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class CollectionDifferenceDemo { public static void main(String[] args) { List<String> setA = new ArrayList<>(Arrays.asList("a", "b", "c", "d")); List<String> setB = new ArrayList<>(Arrays.asList("b", "d", "e")); // 复制setA到新集合,避免修改原数据 List<String> aMinusB = new ArrayList<>(setA); aMinusB.removeAll(setB); System.out.println("A-B的结果:" + aMinusB); // 输出 [a, c] } }
如果使用Set(比如HashSet),效率会更高——因为Set的contains操作是O(1)的哈希查找,而List是O(n)的遍历:
import java.util.HashSet; import java.util.Set; public class SetDifferenceDemo { public static void main(String[] args) { Set<String> setA = new HashSet<>(Set.of("a", "b", "c", "d")); Set<String> setB = new HashSet<>(Set.of("b", "d", "e")); Set<String> aMinusB = new HashSet<>(setA); aMinusB.removeAll(setB); System.out.println("A-B的结果:" + aMinusB); // 输出 [a, c] } }
2. 使用Stream API(更灵活)
Java 8及以上的Stream API可以通过过滤逻辑实现差集,适合需要自定义过滤条件的场景,比如同时要满足其他判断规则:
import java.util.List; import java.util.stream.Collectors; public class StreamDifferenceDemo { public static void main(String[] args) { List<String> setA = List.of("a", "b", "c", "d"); List<String> setB = List.of("b", "d", "e"); List<String> aMinusB = setA.stream() .filter(element -> !setB.contains(element)) .collect(Collectors.toList()); System.out.println("A-B的结果:" + aMinusB); // 输出 [a, c] } }
二、时长区间的A-B操作(你的场景)
你提到的“第一个时长中有30分钟未出现在第二个时长中”,本质是时间区间的差集计算——也就是计算第一个时间区间里,不与第二个区间重叠的部分的总时长。我们完全可以用Java原生的java.time包实现,不需要依赖JodaTime:
1. 封装时间区间类
先写一个简单的时间区间类,包含区间的开始/结束时间,以及计算重叠、差集时长的方法:
import java.time.Duration; import java.time.LocalDateTime; class TimeInterval { private final LocalDateTime start; private final LocalDateTime end; public TimeInterval(LocalDateTime start, LocalDateTime end) { if (start.isAfter(end)) { throw new IllegalArgumentException("开始时间不能晚于结束时间"); } this.start = start; this.end = end; } // 获取当前区间的总时长 public Duration getTotalDuration() { return Duration.between(start, end); } // 判断两个区间是否重叠 private boolean isOverlappingWith(TimeInterval other) { // 重叠条件:当前区间开始早于对方结束,且当前区间结束晚于对方开始 return this.start.isBefore(other.end) && this.end.isAfter(other.start); } // 获取两个区间的重叠部分 private TimeInterval getOverlapWith(TimeInterval other) { if (!isOverlappingWith(other)) { return null; } // 重叠区间的开始是两个区间开始的较晚者 LocalDateTime overlapStart = this.start.isAfter(other.start) ? this.start : other.start; // 重叠区间的结束是两个区间结束的较早者 LocalDateTime overlapEnd = this.end.isBefore(other.end) ? this.end : other.end; return new TimeInterval(overlapStart, overlapEnd); } // 计算当前区间减去另一个区间后的剩余时长(即A-B的时长) public Duration subtractInterval(TimeInterval other) { if (!isOverlappingWith(other)) { // 没有重叠,直接返回原时长 return getTotalDuration(); } // 原时长减去重叠部分的时长 Duration overlapDuration = getOverlapWith(other).getTotalDuration(); return getTotalDuration().minus(overlapDuration); } }
2. 场景测试示例
用你描述的“第一个时长60分钟,第二个时长30分钟且完全重叠后半段”的场景来验证:
import java.time.LocalDateTime; public class TimeIntervalDifferenceDemo { public static void main(String[] args) { // 区间A:10:00 - 11:00(60分钟) LocalDateTime startA = LocalDateTime.of(2024, 5, 20, 10, 0); LocalDateTime endA = LocalDateTime.of(2024, 5, 20, 11, 0); TimeInterval intervalA = new TimeInterval(startA, endA); // 区间B:10:30 - 11:00(30分钟) LocalDateTime startB = LocalDateTime.of(2024, 5, 20, 10, 30); LocalDateTime endB = LocalDateTime.of(2024, 5, 20, 11, 0); TimeInterval intervalB = new TimeInterval(startB, endB); // 计算A-B的剩余时长 Duration remainingDuration = intervalA.subtractInterval(intervalB); System.out.println("A-B的时长:" + remainingDuration.toMinutes() + "分钟"); // 输出30 } }
扩展说明
如果遇到更复杂的场景(比如多个B区间、B完全包含A、B与A部分重叠在开头等),只需要扩展subtractInterval方法的逻辑,比如遍历所有B区间,依次减去重叠部分即可。
内容的提问来源于stack exchange,提问作者Owen
相关产品推荐
相关产品推荐

