昨天总结了一下stream中的一些方法的基础使用,现在做一些其他的总结
方法类型和管道
正如我们一直在讨论的,Java 流操作分为中间操作和终端操作。
诸如 filter() 之类的中间操作会返回一个可以在其上进行进一步处理的新流。诸如 forEach() 之类的终端操作将流标记为已使用,之后就不能再进一步使用它。
流管道由流源、零个或多个中间操作和终端操作组成。
这是一个示例流管线,其中Emplist是源,Filter()是中间操作,计数是终端操作:
@Test public void whenStreamCount_thenGetElementCount() { Long empCount = empList.stream() .filter(e -> e.getSalary() > 200000) .count(); assertEquals(empCount, new Long(1)); } 复制代码
一些操作被认为是短路操作。短路操作允许对无限流的计算在有限时间内完成:
@Test public void whenLimitInfiniteStream_thenGetFiniteElements() { Stream<Integer> infiniteStream = Stream.iterate(2, i -> i * 2); List<Integer> collect = infiniteStream .skip(3) .limit(5) .collect(Collectors.toList()); assertEquals(collect, Arrays.asList(16, 32, 64, 128, 256)); } 复制代码
在这里,我们使用短路操作Skip()跳过前3个元素,并限制()限制使用使用Iterate()生成的无限流中的5个元素。
稍后,我们将更多地谈论无限流。
“懒”加载
Java 流最重要的特性之一是它们允许通过惰性求值进行显着优化。
仅在启动终端操作时才对源数据进行计算,并且仅在需要时消耗源元素。
所有中间操作都是惰性的,因此在实际需要处理结果之前不会执行它们。
例如,考虑我们之前看到的 findFirst() 示例。 map() 操作在这里执行了多少次? 4 次,因为输入数组包含 4 个元素?
@Test public void whenFindFirst_thenGetFirstEmployeeInStream() { Integer[] empIds = { 1, 2, 3, 4 }; Employee employee = Stream.of(empIds) .map(employeeRepository::findById) .filter(e -> e != null) .filter(e -> e.getSalary() > 100000) .findFirst() .orElse(null); assertEquals(employee.getSalary(), new Double(200000)); } 复制代码
Stream 执行映射和两个过滤操作,一次一个元素。
它首先对 id 1 执行所有操作。由于 id 1 的薪水不大于 100000,因此处理转到下一个元素。
Id 2 满足两个过滤器谓词,因此流计算终端操作 findFirst() 并返回结果。
没有对 id 3 和 4 执行任何操作。
延迟处理流可以避免在不必要时检查所有数据。当输入流是无限的而不仅仅是非常大时,这种行为变得更加重要。
Stream的比较操作
sorted
让我们从 sorted() 操作开始——它根据我们传递给它的比较器对流元素进行排序。
例如,我们可以根据员工的姓名对员工进行排序:
@Test public void whenSortStream_thenGetSortedStream() { List<Employee> employees = empList.stream() .sorted((e1, e2) -> e1.getName().compareTo(e2.getName())) .collect(Collectors.toList()); assertEquals(employees.get(0).getName(), "Bill Gates"); assertEquals(employees.get(1).getName(), "Jeff Bezos"); assertEquals(employees.get(2).getName(), "Mark Zuckerberg"); } 复制代码
请注意,短路不会用于排序()。
这意味着在上面的示例中,即使我们在sorted()之后使用了findfirst(),所有元素的排序也是在应用findfirst()之前完成的。发生这种情况是因为操作不知道第一个元素在整个流进行排序之前。
min and max
顾名思义,min() 和 max() 基于比较器分别返回流中的最小和最大元素。它们返回 Optional 因为结果可能存在也可能不存在(例如,由于过滤):
@Test public void whenFindMin_thenGetMinElementFromStream() { Employee firstEmp = empList.stream() .min((e1, e2) -> e1.getId() - e2.getId()) .orElseThrow(NoSuchElementException::new); assertEquals(firstEmp.getId(), new Integer(1)); } 复制代码
我们也可以避免使用 Comparator.comparing() 来定义比较逻辑:
@Test public void whenFindMax_thenGetMaxElementFromStream() { Employee maxSalEmp = empList.stream() .max(Comparator.comparing(Employee::getSalary)) .orElseThrow(NoSuchElementException::new); assertEquals(maxSalEmp.getSalary(), new Double(300000.0)); } 复制代码
distinct
distinct() 不接受任何参数并返回流中的不同元素,消除重复。它使用元素的 equals() 方法来判断两个元素是否相等:
@Test public void whenApplyDistinct_thenRemoveDuplicatesFromStream() { List<Integer> intList = Arrays.asList(2, 5, 3, 2, 4, 3); List<Integer> distinctIntList = intList.stream().distinct().collect(Collectors.toList()); assertEquals(distinctIntList, Arrays.asList(2, 5, 3, 4)); } 复制代码
allMatch, anyMatch, and noneMatch
这些操作都采用谓词并返回布尔值。应用短路并在确定答案后立即停止处理:
@Test public void whenApplyMatch_thenReturnBoolean() { List<Integer> intList = Arrays.asList(2, 4, 5, 6, 8); boolean allEven = intList.stream().allMatch(i -> i % 2 == 0); boolean oneEven = intList.stream().anyMatch(i -> i % 2 == 0); boolean noneMultipleOfThree = intList.stream().noneMatch(i -> i % 3 == 0); assertEquals(allEven, false); assertEquals(oneEven, true); assertEquals(noneMultipleOfThree, false); } 复制代码
AllMatch()检查流中所有元素是否为true。在这里,它一旦遇到5,它就会返回False,这是不可分解的2。
AnyMatch()检查流中任何一个元素是否为true true。在这里,再次应用短路,并在第一个元素之后立即返回TRUE。
nonematch()检查是否没有匹配谓词的元素。在这里,它只会在遇到6的6时立即返回错误,这是可以除以3的3。