原文链接:https://medium.com/javarevisited/10-java-stream-tips-must-read-2063a84af3be


作者&文章简介

  • 作者:Sivaram Rasathurai
  • 开篇作者形容 Java Stream API就像是瑞士军刀一般,小巧&紧凑,可以处理多种多样的任务
  • Java Stream API 为函数式编程风格,能写出无副作用的函数,使代码可以更加简洁的表现
  • 如果想用好 Stream API,需要了解他的最佳实践以及常见的陷阱
  • 此篇文章中,作者将带领大家了解Stream API的十条最佳实践

    Java Stream API 的十条最佳实践

    1、原始类型的 Stream 流性能更优

  • int -> IntStream

  • long -> LongStream
  • double -> DoubleStream

推荐理由:上面的几种原始类型的 Stream 可以避免额外的装箱和拆箱操作

  1. public static void main(String[] args) {
  2. int[] array = new int[]{1, 2, 3, 4, 5};
  3. StopWatch sw1 = new StopWatch();
  4. sw1.start();
  5. for (int i = 0; i < 100 * 10000; i++) {
  6. Arrays.stream(array).sum();
  7. }
  8. sw1.stop();
  9. System.out.println("Arrays.stream : " + sw1.getTotalTimeMillis());
  10. StopWatch sw2 = new StopWatch();
  11. sw2.start();
  12. for (int i = 0; i < 100 * 10000; i++) {
  13. IntStream.of(array).sum();
  14. }
  15. sw2.stop();
  16. System.out.println("IntStream : " + sw2.getTotalTimeMillis());
  17. }
  18. // 分别进行 100 万次计算,结果如下(毫秒):
  19. // Arrays.stream : 104
  20. // IntStream : 29

2、避免嵌套的 Stream 流

推荐理由:嵌套的 Stream 不易读,建议分而治之,将中间结果存储在临时变量中

  1. public static void main(String[] args) {
  2. List<String> list1 = Arrays.asList("apple", "banana", "cherry");
  3. List<String> list2 = Arrays.asList("orange", "pineapple", "mango");
  4. List<String> result = Stream.concat(list1.stream(), list2.stream())
  5. .filter(s -> s.length() > 5)
  6. .collect(Collectors.toList());
  7. }

3、谨慎使用并行流

推荐理由:虽然并行 Stream 流能够提升性能,但是也会带来额外的开销以及条件竞争
我们在使用 parallelStream 的时候要注意数据的大小、操作的复杂度、以及并行数

  1. public static void main(String[] args) {
  2. List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
  3. int sum = list.parallelStream().reduce(0, Integer::sum);
  4. }

4、惰性求值性能更优

推荐理由:在终端操作被调用前,那些中间操作过程不会被执行

  1. public static void main(String[] args) {
  2. List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
  3. Optional<Integer> result = list.stream()
  4. .filter(n -> n > 3)
  5. .findFirst();
  6. }

5、可以避免副作用

推荐理由:**Stream API**是为集合数据运算而设计,应避免修改外部变量等操作

  1. public static void main(String[] args) {
  2. List<String> list = Arrays.asList("apple", "banana", "cherry");
  3. long count = 0;
  4. list.stream()
  5. .filter(s -> s.startsWith("a"))
  6. .forEach(s -> count++);
  7. // 这里的 count++ 会报错:
  8. // Variable used in lambda expression should be final or effectively final
  9. }
  1. public static void main(String[] args) {
  2. List<String> list = Arrays.asList("apple", "banana", "cherry");
  3. long count = list.stream()
  4. .filter(s -> s.startsWith("a"))
  5. .mapToLong(e -> 1L)
  6. .sum();
  7. }

6、使用不可变对象

推荐理由:可以确保在操作过程中对象没有发生修改,可以在阅读代码过程中更加准确的预测其行为

  1. public static void main(String[] args) {
  2. List<String> list = Arrays.asList("apple", "banana", "cherry");
  3. List<String> result = list.stream()
  4. .map(String::toUpperCase)
  5. .collect(Collectors.toList());
  6. }

7、在 map() 前使用 filter() 过滤非必要的数据

推荐理由:特别是处理数据量较大的集合时,先过滤再映射能够提升不少性能

  1. public static void main(String[] args) {
  2. List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
  3. List<Integer> filteredList = list.stream()
  4. .filter(i -> i % 2 == 0)
  5. .map(i -> i * 2)
  6. .collect(Collectors.toList());
  7. }

8、方法引用 优于 Lambda 表达式

推荐理由:使用方法引用比使用 Lambda 表达式更简洁易读

  1. public static void main(String[] args) {
  2. List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
  3. int sum = list.stream()
  4. .reduce(0, Integer::sum);
  5. }

9、使用 distinct() 移除重复项

推荐理由:如小标题所示,在你不需要重复项时可以使用 distinct() 来过滤

  1. public static void main(String[] args) {
  2. List<Integer> list = Arrays.asList(1, 2, 3, 3, 4, 5, 5);
  3. List<Integer> distinctList = list.stream()
  4. .distinct()
  5. .collect(Collectors.toList());
  6. }

10、谨慎使用 sorted()

推荐理由:sorted() 操作消耗较大,特别是要操作的集合数较多时,建议仅当需要时再用

  1. public static void main(String[] args) {
  2. List<Integer> list = Arrays.asList(3, 2, 1);
  3. List<Integer> sortedList = list.stream()
  4. .sorted()
  5. .collect(Collectors.toList());
  6. }

扩展阅读

  • 我在 Java 知识大纲下整理的 Stream API 思维导图:

Stream API

image.png