在工作中时常会使用 Java Stream 对集合进行特殊操作,Stream 虽然能简化代码,但是书写以及阅读性不高。故在此记录常用的 Stream 案例以便在未来工作中查阅和使用(复制粘贴😅)
一、Stream 介绍 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
1.1 Stream 操作流程 创建 Stream:一个数据源(如:集合、数组),获取一个流。
中间操作:一个中间操作链,对数据源的数据进行处理。
终止操作:一个终止操作,执行中间操作链,并产生结果。
1.2 Stream 的创建 通过 Collection 集合创建
1 2 3 4 // 获取顺序流 Stream<String> stream = list.stream(); // 获取并行流 Stream<String> parallelStream = list.parallelStream();
通过 Arrays 创建
1 2 String[] arr = {"hello","world","abc"}; Stream<String> stream = Arrays.stream(arr);
通过 Stream 的静态方法创建
1 2 3 4 5 Stream<String> stream1 = Stream.of("hello","world","abc"); // 获取无限流,迭代 Stream<Integer> stream2 = Stream.iterate(0,(x) -> x + 2); // 获取无限流,生成 Stream<Integer> stream3 = Stream.generate(() -> (int)(Math.random()));
1.3 Stream 的中间操作 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为 “惰性求值”。
筛选与切片
方法 说明 filter(Predicate p) 接收 Lambda , 从流中过滤出元素 distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 limit(long maxSize) 截断流,使其元素不超过给定数量。 skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void test() { List<Person> list = Arrays.asList( new Person(1,"aaa",21), new Person(2,"bbb",22), new Person(3,"ccc",23), new Person(4,"ddd",24), new Person(5,"eee",25) ); // 中间操作 Stream<Person> stream = list.stream() .filter((p) -> p.getAge() > 22) // 过滤得到 id 为 3、4、5 的元素 .skip(1) // 跳过 1 个元素,得到 id 为 4、5 元素 .limit(1); // 指获取 1 个元素,得到 id 为 4 的元素 // 终止操作 stream.forEach(System.out::println); }
映射
方法 说明 map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
1 2 3 4 5 6 7 8 9 10 11 @Test public void test() { List<String> list = Arrays.asList("aaa","bbb","ccc"); // 中间操作 Stream<String> stream = list.stream() .map((str) -> str.toUpperCase()); // 将流中所有元素进行转大写操作 // 终止操作 stream.forEach(System.out::println); }
排序
方法 说明 sorted() 产生一个新流,其中按自然顺序排序 sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序
1 2 3 4 5 6 7 8 9 10 @Test public void test() { List<String> list = Arrays.asList("ccc","aaa","bbb"); // 中间操作 Stream<String> stream = list.stream().sorted(); // 自然排序 // 终止操作 stream.forEach(System.out::println); }
1.4 Stream 的终止操作 终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
查找与匹配
方法 说明 allMatch(Predicate p) 检查是否匹配所有元素。 anyMatch(Predicate p) 检查是否至少匹配一个元素。 noneMatch(Predicate p) 检查是否没有匹配所有元素。 findFirst() 返回第一个元素。 findAny() 返回当前流中的任意元素。 count() 返回流中元素总数。 max(Comparator c) 返回流中最大值。 min(Comparator c) 返回流中最小值。 forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Test public void test() { List<Person> list = Arrays.asList( new Person(1,"aaa",21), new Person(2,"bbb",22), new Person(3,"ccc",23), new Person(4,"ddd",24), new Person(5,"eee",25) ); boolean result = list.stream() .allMatch((p) -> p.getAge() > 22); // 是否所有元素中年龄大于 22 System.out.println(result); Optional<Integer> op = list.stream() .map(Person::getAge) // 获取所有元素的年龄 .max(Integer::compare); // 获取最大年龄 System.out.println(op.get()); }
归约
方法 说明 reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值,返回 T。 reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值,返回 Optional。
1 2 3 4 5 6 7 8 @Test public void test() { List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer result = list.stream().reduce(0, (x,y) -> x + y); // 所有元素进行累加 System.out.println(result); }
收集
方法 说明 collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给 Stream 中元素做汇总的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void test() { List<Person> list = Arrays.asList( new Person(1,"aaa",21), new Person(2,"bbb",22), new Person(3,"ccc",23), new Person(4,"ddd",24), new Person(5,"eee",25) ); List<String> result = list.stream() .map(Person::getName) // 获取所有元素的名字 .collect(Collectors.toList()); // 将名字从流中放到新的集合中 System.out.println(result); }
二、案例汇总 准备测试数据
1 2 3 4 5 6 7 8 9 10 11 12 List<User> userList = new ArrayList<>(); // id: id,name: 姓名,age: 年龄 User user1 = new User(1, "张三", 26); User user2 = new User(2, "张三", 28); User user3 = new User(3, "李四", 24); User user4 = new User(4, "王五", 30); User user5 = new User(4, "王五2", 31); userList.add(user1); userList.add(user2); userList.add(user3); userList.add(user4); userList.add(user5);
2.1 获取 id 集合 1 2 List<Integer> idList = userList.stream().map(User::getId).collect(Collectors.toList()); System.out.println("idList:" + idList);
结果:
2.2 拼接所有用户姓名 1 2 String names = userList.stream().map(User::getName).collect(Collectors.joining(",")); System.out.println("names:" + names);
结果:
2.3 年龄递增排序 1 2 List<User> sortList = userList.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList()); System.out.println("sortList:" + sortList);
结果:
1 sortList:[User [id=3, name=李四, age=24], User [id=1, name=张三, age=26], User [id=2, name=张三, age=28], User [id=4, name=王五, age=30], User [id=4, name=王五2, age=31]]
如要递减,编写 Comparator.comparing(User::getAge).reversed()
。
2.4 过滤出年龄大于 30 的用户 1 2 List<User> filterList = userList.stream().filter(i -> i.getAge() > 30).collect(Collectors.toList()); System.out.println("filterList:" + filterList);
结果:
1 filterList:[User [id=4, name=王五2, age=31]]
2.5 统计年龄小于 30 的用户数量 1 2 long count = userList.stream().filter(i -> i.getAge() < 30).count(); System.out.println("count:" + count);
结果:
2.6 获取年龄最大用户 1 2 User maxUser = userList.stream().max(Comparator.comparing(User::getAge)).get(); System.out.println("maxUser:" + maxUser);
结果:
1 maxUser:User [id=4, name=王五2, age=31]
2.7 获取所有用户年龄总和 1 2 Integer totalAge = userList.stream().collect(Collectors.summingInt(User::getAge)); System.out.println("totalAge:" + totalAge);
结果:
2.8 获取所有用户年龄平均值 1 2 Double averageAge = userList.stream().collect(Collectors.averagingDouble(User::getAge)); System.out.println("averageAge:" + averageAge);
结果:
2.9 包含集合总数,年龄最大值, 年龄总和, 年龄平均值 1 2 DoubleSummaryStatistics statistics = userList.stream().collect(Collectors.summarizingDouble(User::getAge)); System.out.println("count:" + statistics.getCount() + ", max:" + statistics.getMax() + ", sum:" + statistics.getSum() + ", average:" + statistics.getAverage());
结果:
1 count:5, max:31.0, sum:139.0, average:27.8
2.10 List 转成 Map 1 2 3 Map<Integer, User> userMap = userList.stream() .collect(Collectors.toMap(User::getId, Function.identity(), (v1, v2) -> v2)); System.out.println("userMap:" + userMap);
结果:
1 userMap:{1=User [id=1, name=张三, age=26], 2=User [id=2, name=张三, age=28], 3=User [id=3, name=李四, age=24], 4=User [id=4, name=王五2, age=31]}
其中 (v1, v2) -> v2) 表示 v1,v2 相同时 取 v2
2.11 获取名字对应的的 id 集合 同样是 List 转成 Map 。
1 2 3 4 5 6 Map<String, List<Integer>> userMap = userList.stream() .collect(Collectors.groupingBy( User::getName, Collectors.mapping(User::getId, Collectors.toList())) ); System.out.println("userMap:" + userMap);
结果:
1 userMap:{李四=[3], 王五2=[4], 张三=[1, 2], 王五=[4]}