一、Stream简介
Java 8 引入了 Stream API,它提供了一种新的处理集合和数组的方式。Stream(流)是一个用于处理数据集合的抽象概念,它可以进行各种操作来处理和转换数据。下面是对 Java 中的 Stream 的详细解释:
- 流的创建:流可以从各种数据源创建,包括集合、数组、I/O通道、生成器等。常见的创建流的方式是通过集合的 stream() 方法或数组的 Arrays.stream() 方法。
- 中间操作:中间操作是对流进行转换和处理的操作,它们可以按需应用于流上。常见的中间操作包括过滤、映射、排序、去重、限制元素数量等。这些操作并不会立即执行,而是返回一个新的流。
- 终端操作:终端操作是对流最终结果的处理操作,它会触发流的遍历和计算。常见的终端操作包括收集结果到集合或数组、聚合操作(如求和、最大值、最小值)、遍历元素等。
- 惰性求值:流的中间操作是惰性求值的,只有在执行终端操作时才会触发中间操作的执行。这种特性使得可以对大型数据集进行高效的操作,只计算必要的结果。
- 并行流:流支持并行处理,可以通过 parallel() 方法将流转换为并行流。在并行流中,操作会被自动并行化,以提高处理大数据量的效率。
- 流的不可变性:流的操作不会改变源数据,而是返回一个新的流。这种不可变性使得流操作更安全和可靠,并且可以进行链式操作。
- 可消费性:流只能被遍历或消费一次,一旦遍历完成或终端操作完成,流就无法再使用。如果需要再次对同一数据集进行处理,需要重新创建新的流。
二、使用流程
Java Stream的使用流程通常由三个步骤组成:创建Stream、对Stream进行中间操作、对Stream进行终止操作。其中,中间操作可以是过滤、映射、排序等,终止操作可以是计数、查找、归约等。
1、将集合转换为Stream流(或者创建流)。
2、操作Stream流(中间操作,终端操作)。
stream流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
三、Stream的创建
生成流的方式主要有五种
1、Stream调用自身方法生成
Stream<Integer> stringStream = Stream.of(1,2 , 3);
2、集合类对象创建(常用)
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Stream<String> stream = list.stream();
3、Array数组创建
String[] stringArr = {"1", "2", "3"};
Stream<String> streamStr = Arrays.stream(stringArr);
注:使用数值流可以避免计算过程中拆箱装箱,提高性能。
Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流【即Stream 】转换成对应的数值流,同时提供了boxed方法将数值流转换为对象流
4、文件创建
Stream<String> fileStream = Files.lines(Paths.get("file.txt"), Charset.defaultCharset());
5、函数创建
Iterator 返回由函数迭代应用于初始元素seed产生的无限有序流
Stream<Integer> iterate = Stream.iterate(0, n -> n + 1).limit(10);
iterate方法接受两个参数,第一个为初始化值,第一次运行时n=0,第二个为进行的函数操作,iterator生成的流为无限流,需要通过limit方法对流进行了截断,最后该代码生产1-10。
四、操作符
流的操作类型主要分为两种:中间操作符、终端操作符
1、中间操作符
Stream API 提供了多种中间操作符,用于对流进行转换、筛选、映射等操作。下面列举一些常用的中间操作符:
- filter(Predicate predicate):根据给定的条件筛选元素,保留满足条件的元素。
- map(Function<T, R> mapper):将元素按照指定的映射规则进行转换,生成新的元素。
- flatMap(Function<T, Stream> mapper):将每个元素映射为一个流,并将所有流合并为单个流。
- distinct():去除流中的重复元素。
- sorted():对流中的元素进行自然排序(要求元素实现 Comparable 接口)。
- sorted(Comparator comparator):根据指定的比较器对流中的元素进行排序。
- limit(long maxSize):限制流的大小,截断流使其最多包含指定数量的元素。
- skip(long n):跳过流中的前 n 个元素,返回剩余的元素流。
- peek(Consumer action):对每个元素执行操作,类似于 forEach,但不会终止流。
- takeWhile(Predicate predicate):从开头开始取元素,直到遇到不满足条件的元素为止。
- dropWhile(Predicate predicate):从开头开始丢弃元素,直到遇到不满足条件的元素为止。
- parallel():将流转换为并行流,以支持并行处理。
举例:
List<Student> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Student student = new Student();
student.setId((1111111111+i));
student.setName(String.valueOf(i));
student.setAge(i);
list.add(student);
}
//使用filter,筛选age大于50的对象
List<Student> collect = list.stream().filter(student -> student.getAge() > 50).collect(Collectors.toList());
//使用filter以及map,将age大于50的学生姓名输出
List<String> collect = list.stream().filter(student -> student.getAge() > 50).map(Student::getName).collect(Collectors.toList());
2、终端操作符
Stream流的终端操作执行后,该流将无法再执行其他动作。如果试图执行其他操作,会导致状态异常并提示该流已经被执行操作或者被关闭。因此为了再次执行操作,必须重新创建Stream流。
需要注意的是,一个流有且只能有一个终端操作。一旦这个终端操作执行后,流就会被关闭,无法再被操作。因此,一个流只能被遍历一次。如果需要在遍历后再次操作流,必须通过源数据重新生成流。
终端操作的执行,才会真正开始流的遍历。如 count、collect 等
- Collect:收集器,将流转换为其他形式
- forEach:对该流的每个元素执行一个操作,遍历流中的数据。
- findFirst:返回第一个元素
- findAny:返回当前流中的任意元素
- sum:返回此流中元素的总和。
- count:返回流中元素总数
- max:返回此流的最大元素
- min:返回此流的最小元素
- anyMatch:返回此流的任何元素是否与所提供的条件匹配。只有有一个匹配则返回ture,否则返回false。如果流为空,则返回false。
检查是否至少匹配一个元素,返回boolean - allMatch:返回此流的所有元素是否与所提供的条件匹配。如果全部匹配则返回true,否则返回false。如果流为空,则返回true。
- noneMatch:返回此流中是否没有元素与所提供的条件匹配。检查是否没有匹配所有元素,返回boolean。
- reduce:使用关联累加函数对该流的元素执行约简操作,并返回一个描述约简值的Optional(如果有的话)。也就是对流中的元素进行累计的操作
3、Collect收集器详解
// 将数据存放到List集合中
List<String> list = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
// 将数据存放到Set集合中
Set<String> set = people.stream()
.map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new));
// 将元素转换为字符串并连接它们,用逗号分隔
String joined = things.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
// 计算员工工资总额
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
// 按部门分组员工
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// 按部门计算工资总额
Map<Department, Integer> totalByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.summingInt(Employee::getSalary)));
// 把学生分为及格和不及格
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
结束
Stream API 提供了一种简洁、强大和可读性高的方式来处理数据集合。它的函数式风格和操作链式调用使得代码更加简洁和易于理解。通过合理地使用 Stream API,可以实现高效、简洁和可维护的数据处理代码。
如果大家觉得文章内容不错,快去分享给更多小伙伴吧。