常见 Stream 流表达式
总体结构图
一、两大类型
中间操作(Intermediate Operations)
中间操作是指在Stream上执行的操作, 它们返回一个新的Stream, 允许你链式地进行多个中间操作.
终端操作(Terminal Operations)
对Stream进行最终处理的操作, 当调用终端操作时, Stream会开始执行中间操作, 并生成最终的结果或副作用.终端操作是Stream的"触发器", 一旦调用终端操作, Stream就不能再被使用, 也不能再进行中间操作.
二、中间操作
2.1 filter
用于根据指定条件过滤元素.它接收一个条件作为参数, 只保留满足条件的元素, 并生成一个新的Stream.
示例
/** TODO ************************************** filter ************************************** */public static void filter() {List<String> tempList = Arrays.asList("小芳", "小李", "小林", "小王");List<String> resList = tempList.stream().filter(s -> s.contains("王")).collect(Collectors.toList());System.out.println(resList.toString());}
输出结果
[小王]
2.2 map
用于对每个元素执行映射操作, 将元素转换成另一种类型.它接收一个Function(映射函数)作为参数, 对每个元素应用该映射函数, 并生成一个新的Stream.
示例
/** TODO ************************************** map ************************************** */public static void map() {List<String> tempList = Arrays.asList("小芳", "小李", "小林", "小王");List<String> resList = tempList.stream().map(s -> "姓名: " + s).collect(Collectors.toList());System.out.println(resList.toString());}
输出结果
[姓名: 小芳, 姓名: 小李, 姓名: 小林, 姓名: 小王]
2.3 flatMap
类似于map操作,但是 flatMap 操作可以将每个元素映射成一个 Stream,然后把所有生成的 Stream 合并成一个新的Stream。
示例
新建一个静态内部类, 然后聚合类中的集合数据
@Data
static class Personnel {// 人员姓名private String name;// 人员标签private List<String> tagList;public Personnel(String name, List<String> tagList) {this.name = name;this.tagList = tagList;}
}
Tips: 就现在想要把 List 中的 tagList 聚合后进行处理, 代码如下:
public static void main(String[] args) {Personnel personA = new Personnel("张三", Arrays.asList("抽烟", "喝酒", "烫头"));Personnel personB = new Personnel("李斯", Arrays.asList("编码", "喝酒", "踢足球"));List<Personnel> personnelList = Arrays.asList(personA, personB);personnelList.stream().flatMap(p -> p.getTagList().stream()).forEach(s -> System.out.print(s + " "));
}
输出结果
抽烟 喝酒 烫头 编码 喝酒 踢足球
2.4 sorted
用于对Stream中的元素进行排序,默认按照自然顺序进行排序。也可以传入自定义的Comparator来指定排序规则。
/** TODO ************************************** sorted ************************************** */public static void sorted() {List<Integer> numList = Arrays.asList(10, 20, 18, 300, 30, 2);// ① 默认排序List<Integer> orderList = numList.stream().sorted().collect(Collectors.toList());System.out.printf("① 默认排序: %s%n", orderList);// ② 自定义排序List<Integer> orderDescList = numList.stream().sorted((x, y) -> {return y.compareTo(x);}).collect(Collectors.toList());System.out.printf("② 自定义排序: %s%n", orderDescList);}
输出结果
① 默认排序: [2, 10, 18, 20, 30, 300]
② 自定义排序: [300, 30, 20, 18, 10, 2]
2.5 distinct
用于去除 Stream 中重复的元素,确保最终的 Stream 中每个元素都是唯一的。
示例
/** TODO ************************************** distinct ************************************** */public static void distinct() {List<Integer> numList = Arrays.asList(1,1,1,1,2,3,2,2);List<Integer> distinctList = numList.stream().distinct().collect(Collectors.toList());System.out.println(distinctList);}
输出结果
[1, 2, 3]
2.6 limit
用于限制Stream的大小,返回一个最大包含前n个元素的新Stream。
示例
/** TODO ************************************** limit ************************************** */public static void limit(){List<Integer> numList = Arrays.asList(1,2,3,4,5,6,7,8);List<Integer> limitList = numList.stream().limit(4).collect(Collectors.toList());System.out.println(limitList);}
输出结果
[1, 2, 3, 4]
2.7 skip
用于跳过Stream中的前n个元素,返回一个丢弃了前n个元素后剩余元素的新Stream。
示例
/** TODO ************************************** skip ************************************** */public static void skip(){List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);List<Integer> skipList = numList.stream().skip(numList.size() - 2).collect(Collectors.toList());System.out.println(skipList);}
输出结果
[7, 8]
2.8 peek
用于对每个元素执行一个操作,同时保持Stream的流。它可以用于调试或记录Stream中的元素。
示例
/** TODO ************************************** peek ************************************** */public static void peek(){List<Integer> numList = Arrays.asList(5, 6, 7, 8);List<Integer> resList = numList.stream().peek(System.out::println).filter(s -> s == 5).peek(s -> System.out.printf("过滤后的:%d%n", s)).collect(Collectors.toList());}
输出结果
5
过滤后的:5
6
7
8
三、终端操作
在Java Stream API中,终端操作(Terminal Operations)是对Stream进行最终处理的操作。
当调用终端操作时,Stream会开始执行中间操作,并生成最终的结果或副作用。
终端操作是Stream的触发器,一旦调用终端操作,Stream就不能再被使用,也不能再进行中间操作。
3.1 forEach
对Stream中的每个元素执行指定的操作,接收一个Consumer(消费者函数)作为参数。它通常用于对Stream中的元素进行输出或执行某些操作,但不会返回任何结果。
示例
/** TODO ************************************** forEach ************************************** */public static void forEach(){// 给公司工资普涨 500List<Integer> salaryList = Arrays.asList(12000, 20000, 30000, 4000);salaryList.stream().peek(s -> System.out.print("工资普涨前:" + s)).map(s -> s + 500).forEach(s -> {System.out.println("--工资普涨后:" + s);});}
输出结果
工资普涨前:12000--工资普涨后:12500
工资普涨前:20000--工资普涨后:20500
工资普涨前:30000--工资普涨后:30500
工资普涨前:4000--工资普涨后:4500
3.2 collect
用于将Stream中的元素收集到一个容器中,接收一个Collector(收集器)作为参数。
它允许你在Stream中执行各种集合操作,例如将元素收集到List、Set、Map等容器中。
示例
把 User 实体集合转换为 Map 集合,名字作为 key,工资作为 Name
/** TODO ************************************** collect ************************************** */public static void collectTest(){List<User> userList = Arrays.asList(new User("张三", 2000.5),new User("李斯", 11000.5),new User("王二", 12000.5),new User("张六", 32000.5),new User("赵公子", 1000000.0));Map<String, Double> userSalaryMap = userList.stream().collect(Collectors.toMap(User::getName, User::getSalary));userSalaryMap.forEach((k, v) -> {System.out.printf("姓名:%s,工资:%.2f%n", k, v);});}@Data
@AllArgsConstructor
static class User {private String name;private Double salary;
}
输出结果
姓名:张三,工资:2000.50
姓名:赵公子,工资:1000000.00
姓名:张六,工资:32000.50
姓名:李斯,工资:11000.50
姓名:王二,工资:12000.50
3.3 toArray
将Stream中的元素转换成一个数组。返回一个包含所有元素的数组,返回的数组类型是根据流元素的类型自动推断的。如果流是空的,将返回一个长度为0的数组。
示例
/** TODO ************************************** toArray ************************************** */public static void toArray(){// 示例整数流IntStream intStream = IntStream.of(1, 2, 3, 4, 5);// 使用toArray()将流中的元素收集到一个数组中int[] intArray = intStream.toArray();// 输出结果数组System.out.println(Arrays.toString(intArray));}
输出结果
[1, 2, 3, 4, 5]
3.4 reduce
Stream 类的 reduce() 方法是用于将流中的元素进行归约操作的方法。
接收一个 BinaryOperator(二元运算函数作为参数,用于对两个元素进行操作,并返回一个合并后的结果。
它可以将流中的所有元素按照指定的规则进行合并,并返回一个 Optional 对象,因为流可能为空。
示例
/** TODO ************************************** reduce ************************************** */public static void reduceTest(){// 示例整数流IntStream intStream = IntStream.of(1, 2, 3, 4, 5);// 使用reduce()将流中的整数相加得到总和OptionalInt sumOptional = intStream.reduce((a, b) -> a + b);// 获取结果总和,如果流为空,则给出一个默认值0int sum = sumOptional.orElse(0);// 输出结果总和System.out.println("总和: " + sum);}
输出结果
总和: 15
3.5 min / max
Stream 类的 min() 和 max() 方法是用于查找流中的最小值和最大值的终端操作。它们接受一个 Comparator 对象作为参数来确定元素的顺序,并返回一个 Optional 对象,因为流可能为空。
示例
假设我们有一个包含整数的流,并且我们想找到其中的最小值和最大值
/** TODO ************************************** min / max ************************************** */public static void minAndMaxTest(){// 示例整数流Stream<Integer> integerStream = Stream.of(1, 5, 3, 8, 2);// 使用min()找到最小值Optional<Integer> minOptional = integerStream.min(Integer::compareTo);if (minOptional.isPresent()) {System.out.println("最小值为: " + minOptional.get());} else {System.out.println("流为空.");}// 重新创建一个整数流,因为流已被消耗Stream<Integer> newIntegerStream = Stream.of(1, 5, 3, 8, 2);// 使用max()找到最大值Optional<Integer> maxOptional = newIntegerStream.max(Integer::compareTo);if (maxOptional.isPresent()) {System.out.println("最大值为: " + maxOptional.get());} else {System.out.println("流为空.");}}
输出结果
最小值为: 1
最大值为: 8
3.6 count
Stream 类的 count() 方法是用于计算流中元素个数的终端操作。它返回一个 long 类型的值,表示流中的元素数量。
count() 方法是一个终端操作,一旦调用该方法,流就被消耗,无法再次使用。
示例
/** TODO ************************************** count ************************************** */public static void count() {List<Integer> numList = Arrays.asList(11, 22, 1, 2, 3, 4, 6, 33, 44, 553);long count = numList.stream().filter(s1 -> s1 > 10).count();System.out.println("大于10的个数数是:" + count);}
输出结果
大于10的个数数是:5
3.7 anyMatch / allMatch / noneMatch
Stream 类的 anyMatch(), allMatch(), 和 noneMatch() 是用于检查流中元素是否满足特定条件的终端操作。
它们返回一个布尔值,表示流中的元素是否满足指定的条件。这些方法在遇到满足条件的元素后可能会提前终止流的处理。
- anyMatch检查是否有任意元素满足条件
- allMatch检查是否所有元素都满足条件
- noneMatch检查是否没有元素满足条件。
示例
/*** TODO ************************************** anyMatch / allMatch / noneMatch ***************************************/public static void matchTest() {// 示例整数流Stream<Integer> integerStream = Stream.of(1, 5, 3, 8, 2);// 使用anyMatch()检查是否存在元素大于5boolean anyGreaterThan5 = integerStream.anyMatch(num -> num > 4);System.out.println("是否存在元素大于 5 ?" + anyGreaterThan5);// 重新创建一个整数流,因为流已被消耗Stream<Integer> newIntegerStream = Stream.of(1, 5, 3, 8, 2);// 使用allMatch()检查是否所有元素都小于10boolean allLessThan10 = newIntegerStream.allMatch(num -> num < 10);System.out.println("所有元素都小于10 ? " + allLessThan10);// 重新创建一个整数流,因为流已被消耗Stream<Integer> newestIntegerStream = Stream.of(1, 5, 3, 8, 2);// 使用noneMatch()检查是否没有元素等于10boolean noneEqualTo10 = newestIntegerStream.noneMatch(num -> num == 10);System.out.println("是否没有元素等于 10 ? " + noneEqualTo10);}
输出结果
是否存在元素大于 5 ?true
所有元素都小于10 ? true
是否没有元素等于 10 ? true
3.8 findFirst / findAny
Stream 类的 findFirst() 和 findAny() 方法用于在流中查找元素的终端操作.
它们都返回一个 Optional 对象,表示找到的元素或元素的可能性。
在并行流中,findAny() 方法可能更快,因为它不一定要遍历所有元素。
在串行 Stream 中,findFirst()和 findAny() 返回的是相同的元素,
在并行Stream中,findAny()返回的是最先找到的元素。
示例
假设我们有一个包含整数的流,并且我们想查找其中的某个元素。
/*** TODO ************************************** findFirst / findAny ***************************************/public static void FindStreamTest () {// 示例整数流Stream<Integer> integerStream = Stream.of(1, 5, 3, 8, 2);// 使用findFirst()找到第一个元素Optional<Integer> firstElementOptional = integerStream.findFirst();if (firstElementOptional.isPresent()) {System.out.println("发现第一个元素: " + firstElementOptional.get());} else {System.out.println("流为空!");}// 重新创建一个整数流,因为流已被消耗Stream<Integer> newIntegerStream = Stream.of(1, 5, 3, 8, 2);// 使用findAny()找到任意一个元素Optional<Integer> anyElementOptional = newIntegerStream.findAny();if (anyElementOptional.isPresent()) {System.out.println("找到任意一个元素: " + anyElementOptional.get());} else {System.out.println("流为空!");}}
输出结果
发现第一个元素: 1
找到任意一个元素: 1