45. 【Java教程】流式操作

2024-06-18 17:20
文章标签 java 教程 操作 45 流式

本文主要是介绍45. 【Java教程】流式操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

流式操作,是 Java 8 除了Lambda表达式外的又一重大改变。学习流式操作,就是学习java.util.stream包下的API,我们称之为Stream API,它把真正的函数式编程引入到了 Java 中。

本小节我们将了解到什么是Stream为什么使用Stream API, 流式操作的执行流程如何实例化StreamStream的中间操作Stream的终止操作等内容。

1. 什么是 Stream

Stream是数据渠道,用于操作数据源所生成的元素序列,它可以实现对集合(Collection)的复杂操作,例如查找、替换、过滤和映射数据等操作。

我们这里说的Stream不同于java的输入输出流。另外,Collection 是一种静态的数据结构,存储在内存中,而Stream是用于计算的,通过CPU实现计算。注意不要混淆。

TipsStream自己不会存储数据;Stream不会改变源对象,而是返回一个新的持有结果的Stream(不可变性);Stream操作是延迟执行的(这一点将在后面介绍)。

2. 为什么使用 Stream API

我们在实际开发中,项目中的很多数据都来源于关系型数据库(例如 MySQL、Oracle 数据库),我们使用SQL的条件语句就可以实现对数据的筛选、过滤等等操作;

但也有很多数据来源于非关系型数据库(RedisMongoDB等),想要处理这些数据,往往需要在 Java 层面去处理。

使用Stream API对集合中的数据进行操作,就类似于 SQL 执行的数据库查询。也可以使用Stream API来执行并行操作。简单来说,Stream API提供了一种高效且易于使用的处理数据的方式。

3. 流式操作的执行流程

流式操作通常分为以下 3 个步骤:

  1. 创建Stream对象:通过一个数据源(例如集合、数组),获取一个流;

  2. 中间操作:一个中间的链式操作,对数据源的数据进行处理(例如过滤、排序等),直到执行终止操作才执行;

  3. 终止操作:一旦执行终止操作,就执行中间的链式操作,并产生结果。

下图展示了Stream的执行流程:

接下来我们就按照这 3 个步骤的顺序来展开学习Stream API

4. Stream 对象的创建

有 4 种方式来创建Stream对象。

4.1 通过集合创建 Stream

Java 8 的java.util.Collection 接口被扩展,提供了两个获取流的默认方法:

  • default Stream<E> stream():返回一个串行流(顺序流);

  • default Stream<E> parallelStream():返回一个并行流。

实例如下:

// 创建一个集合,并添加几个元素  
List<String> stringList = new ArrayList<>();  
stringList.add("hello");  
stringList.add("world");  
stringList.add("java");  
​  
// 通过集合获取串行 stream 对象  
Stream<String> stream = stringList.stream();  
// 通过集合获取并行 stream 对象  
Stream<String> personStream = stringList.parallelStream();

串行流并行流的区别是:串行流从集合中取数据是按照集合的顺序的;而并行流是并行操作的,获取到的数据是无序的。

4.2 通过数组创建 Stream

Java 8 中的java.util.Arrays的静态方法stream()可以获取数组流:

  • static <T> Stream<T> stream(T[] array):返回一个数组流。

此外,stream()还有几个重载方法,能够处理对应的基本数据类型的数组:

  • public static IntStream stream(int[] array):返回以指定数组作为其源的连续IntStream

  • public static LongStream stream(long[] array):返回以指定数组作为其源的连续LongStream

  • public static DoubleStream stream(double[] array):返回以指定数组作为其源的连续DoubleStream

实例如下:

import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;public class StreamDemo1 {public static void main(String[] args) {// 初始化一个整型数组int[] arr = new int[]{1,2,3};// 通过整型数组,获取整形的 stream 对象IntStream stream1 = Arrays.stream(arr);// 通过字符串类型的数组,获取泛型类型为 String 的 stream 对象String[] stringArr = new String[]{"Hello", "mybj"};Stream<String> stream2 = Arrays.stream(stringArr);}
}

4.3 通过 Stream 的 of()方法

可以通过Stream类下的of()方法来创建 Stream 对象,实例如下:

import java.util.stream.Stream;public class StreamDemo1 {public static void main(String[] args) {// 通过 Stream 类下的 of() 方法,创建 stream 对象、Stream<Integer> stream = Stream.of(1, 2, 3);}
}

4.4 创建无限流

可以使用Stream类下的静态方法iterate()以及generate()创建无限流:

  • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f):遍历;

  • public static<T> Stream<T> generate(Supplier<T> s):生成。

创建无限流的这种方式实际使用较少,大家了解一下即可。

5. Stream 的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。在终止操作时会一次性全部处理这些中间操作,称为“惰性求值”。下面,我们来学习一下常用的中间操作方法。

5.1 筛选与切片

关于筛选和切片中间操作,有下面几个常用方法:

  • filter(Predicate p):接收 Lambda,从流中清除某些元素;

  • distinct():筛选,通过流生成元素的hashCodeequals()方法去除重复元素;

  • limit(long maxSize):截断流,使其元素不超过给定数量;

  • skip(long n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与limit(n)互补。

我们先来看一个过滤集合元素的实例:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;public class StreamDemo2 {static class Person {private String name;private int age;public Person() { }public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}}/*** 创建一个Person的集合* @return List*/public static List<Person> createPeople() {ArrayList<Person> people = new ArrayList<>();Person person1 = new Person("小明", 15);Person person2 = new Person("小芳", 20);Person person3 = new Person("小李", 18);Person person4 = new Person("小付", 23);Person person5 = new Person("大飞", 22);people.add(person1);people.add(person2);people.add(person3);people.add(person4);people.add(person5);return people;}public static void main(String[] args) {List<Person> people = createPeople();// 创建 Stream 对象Stream<Person> stream = people.stream();// 过滤年龄大于 20 的personStream<Person> personStream = stream.filter(person -> person.getAge() >= 20);// 触发终止操作才能执行中间操作,遍历列表中元素并打印personStream.forEach(System.out::println);}
}

运行结果:

Person{name='小芳', age=20}  
Person{name='小付', age=23}  
Person{name='大飞', age=22}

实例中,有一个静态内部类Person以及一个创建Person的集合的静态方法createPeople(),在主方法中,我们先调用该静态方法获取到一个Person列表,然后创建了Stream对象,再执行中间操作(即调用fliter()方法),这个方法的参数类型是一个断言型的函数式接口,接口下的抽象方法test()要求返回boolean结果,因此我们使用Lambda表达式,Lambda体为person.getAge() >= 20,其返回值就是一个布尔型结果,这样就实现了对年龄大于等于 20 的person对象的过滤。

由于必须触发终止操作才能执行中间操作,我们又调用了forEach(System.out::println),在这里记住它作用是遍历该列表并打印每一个元素即可,我们下面将会讲解。另外,filter()等这些由于中间操作返回类型为 Stream,所以支持链式操作,我们可以将主方法中最后两行代码合并成一行:

stream.filter(person -> person.getAge() >= 20).forEach(System.out::println);

我们再来看一个截断流的使用实例:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;public class StreamDemo3 {static class Person {private String name;private int age;public Person() { }public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}}/*** 创建一个Person的集合* @return List*/public static List<Person> createPeople() {ArrayList<Person> people = new ArrayList<>();Person person1 = new Person("小明", 15);Person person2 = new Person("小芳", 20);Person person3 = new Person("小李", 18);Person person4 = new Person("小付", 23);Person person5 = new Person("大飞", 22);people.add(person1);people.add(person2);people.add(person3);people.add(person4);people.add(person5);return people;}public static void main(String[] args) {List<Person> people = createPeople();// 创建 Stream 对象Stream<Person> stream = people.stream();// 截断流,并调用终止操作打印集合中元素stream.limit(2).forEach(System.out::println);}
}

运行结果:

Person{name='小明', age=15}  
Person{name='小芳', age=20}

根据运行结果显示,我们只打印了集合中的前两条数据。

跳过前 2 条数据的代码实例如下:

// 非完整代码
public static void main(String[] args) {List<Person> people = createPeople();// 创建 Stream 对象Stream<Person> stream = people.stream();// 跳过前两个元素,并调用终止操作打印集合中元素stream.skip(2).forEach(System.out::println);
}

运行结果:

Person{name='小李', age=18}  
Person{name='小付', age=23}  
Person{name='大飞', age=22}

distinct()方法会根据equals()hashCode()方法筛选重复数据,我们在Person类内部重写这两个方法,并且在createPerson()方法中,添加几个重复的数据 ,实例如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;public class StreamDemo4 {static class Person {private String name;private int age;public Person() { }public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age &&Objects.equals(name, person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}}/*** 创建一个Person的集合* @return List*/public static List<Person> createPeople() {ArrayList<Person> people = new ArrayList<>();people.add(new Person("小明", 15));people.add(new Person("小芳", 20));people.add(new Person("小李", 18));people.add(new Person("小付", 23));people.add(new Person("小付", 23));people.add(new Person("大飞", 22));people.add(new Person("大飞", 22));people.add(new Person("大飞", 22));return people;}public static void main(String[] args) {List<Person> people = createPeople();// 创建 Stream 对象Stream<Person> stream = people.stream();System.out.println("去重前,集合中元素有:");stream.forEach(System.out::println);System.out.println("去重后,集合中元素有:");// 创建一个新流Stream<Person> stream1 = people.stream();// 截断流,并调用终止操作打印集合中元素stream1.distinct().forEach(System.out::println);}
}

运行结果:

去重前,集合中元素有:  
Person{name='小明', age=15}  
Person{name='小芳', age=20}  
Person{name='小李', age=18}  
Person{name='小付', age=23}  
Person{name='小付', age=23}  
Person{name='大飞', age=22}  
Person{name='大飞', age=22}  
Person{name='大飞', age=22}  
去重后,集合中元素有:  
Person{name='小明', age=15}  
Person{name='小芳', age=20}  
Person{name='小李', age=18}  
Person{name='小付', age=23}  
Person{name='大飞', age=22}

5.2 映射

关于映射中间操作,有下面几个常用方法:

  • map(Function f):接收一个方法作为参数,该方法会被应用到每个元素上,并将其映射成一个新的元素;

  • mapToDouble(ToDoubleFunction f):接收一个方法作为参数,该方法会被应用到每个元素上,产生一个新的DoubleStream

  • mapToLong(ToLongFunction f):接收一个方法作为参数,该方法会被应用到每个元素上,产生一个新的LongStream

  • flatMap(Function f):接收一个方法作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

请查看如下实例:

import java.util.Arrays;
import java.util.List;public class StreamDemo5 {public static void main(String[] args) {// 创建一个包含小写字母元素的字符串列表List<String> stringList = Arrays.asList("php", "js", "python", "java");// 调用 map() 方法,将String下的toUpperCase()方法作为参数,这个方法会被应用到每个元素上,映射成一个新元素,最后打印映射后的元素stringList.stream().map(String::toUpperCase).forEach(System.out::println);}}

运行结果:

PHP  
JS  
PYTHON  
JAVA

可参考下图,理解映射的过程:

5.3 排序

关于排序中间操作,有下面几个常用方法:

  • sorted():产生一个新流,其中按照自然顺序排序;

  • sorted(Comparator com):产生一个新流,其中按照比较器顺序排序。

请查看如下实例:

import java.util.Arrays;
import java.util.List;public class StreamDemo6 {public static void main(String[] args) {List<Integer> integers = Arrays.asList(10, 12, 9, 8, 20, 1);// 调用sorted()方法自然排序,并打印每个元素integers.stream().sorted().forEach(System.out::println);}}

运行结果:

1  
8  
9  
10  
12  
20

上面实例中,我们调用sorted()方法对集合元素进行了从小到大的自然排序,那么如果想要实现从大到小排序,任何实现呢?此时就要用到sorted(Comparator com)方法定制排序,查看如下实例:

import java.util.Arrays;
import java.util.List;public class StreamDemo6 {public static void main(String[] args) {List<Integer> integers = Arrays.asList(10, 12, 9, 8, 20, 1);// 定制排序integers.stream().sorted((i1, i2) -> -Integer.compare(i1, i2)).forEach(System.out::println);}}

运行结果:

20
12
10
9
8
1

实例中,sorted()方法接收的参数是一个函数式接口Comparator,因此使用Lambda表达式创建函数式接口实例即可,Lambda体调用整型的比较方法,对返回的整型值做一个取反即可。

6. Stream 的终止操作

执行终止操作会从流的流水线上生成结果,其结果可以是任何不是流的值,例如ListStringvoid

在上面实例中,我们一直在使用forEach()方法来执行流的终止操作,下面我们看看还有哪些其他终止操作。

6.1 匹配与查找

关于匹配与查找的终止操作,有下面几个常用方法:

  • allMatch(Predicate p):检查是否匹配所有元素;

  • anyMatch(Predicate p):检查是否至少匹配一个元素;

  • noneMatch(Predicate p):检查是否没有匹配所有元素;

  • findFirst():返回第一个元素;

  • findAny():返回当前流中的任意元素;

  • count():返回流中元素总数;

  • max(Comparator c):返回流中最大值;

  • min(Comparator c):返回流中最小值;

  • forEach(Consumer c):内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代;相反 Stream API使用内部迭代)。

如下实例,演示了几个匹配元素相关方法的使用:

import java.util.Arrays;
import java.util.List;public class StreamDemo7 {public static void main(String[] args) {// 创建一个整型列表List<Integer> integers = Arrays.asList(10, 12, 9, 8, 20, 1);// 使用 allMatch(Predicate p) 检查是否匹配所有元素,如果匹配,则返回 true;否则返回 falseboolean b1 = integers.stream().allMatch(integer -> integer > 0);if (b1) {System.out.println(integers + "列表中所有的元素都大于0");} else {System.out.println(integers + "列表中不是所有的元素都大于0");}// 使用 anyMatch(Predicate p) 检查是否至少匹配一个元素boolean b2 = integers.stream().anyMatch(integer -> integer >= 20);if (b2) {System.out.println(integers + "列表中至少存在一个的元素都大于等于20");} else {System.out.println(integers + "列表中不存在任何一个大于等于20的元素");}// 使用 noneMath(Predicate p) 检查是否没有匹配所有元素boolean b3 = integers.stream().noneMatch(integer -> integer > 100);if (b3) {System.out.println(integers + "列表中不存在大于100的元素");} else {System.out.println(integers + "列表中存在大于100的元素");}}}

运行结果:

[10, 12, 9, 8, 20, 1]列表中所有的元素都大于0
[10, 12, 9, 8, 20, 1]列表中至少存在一个的元素都大于等于20
[10, 12, 9, 8, 20, 1]列表中不存在大于100的元素

查找元素的相关方法使用实例如下:

import java.util.Arrays;
import java.util.List;
import java.util.Optional;public class StreamDemo8 {public static void main(String[] args) {// 创建一个整型列表List<Integer> integers = Arrays.asList(10, 12, 9, 8, 20, 1);// 使用 findFirst() 获取当前流中的第一个元素Optional<Integer> first = integers.stream().findFirst();System.out.println(integers + "列表中第一个元素为:" + first);// 使用 findAny() 获取当前流中的任意元素Optional<Integer> any = integers.stream().findAny();System.out.println("列表中任意元素:" + any);// 使用 count() 获取当前流中元素总数long count = integers.stream().count();System.out.println(integers + "列表中元素总数为" + count);// 使用 max(Comparator c) 获取流中最大值Optional<Integer> max = integers.stream().max(Integer::compare);System.out.println(integers + "列表中最大值为" + max);// 使用 min(Comparator c) 获取流中最小值Optional<Integer> min = integers.stream().min(Integer::compare);System.out.println(integers + "列表中最小值为" + min);}}

运行结果:

[10, 12, 9, 8, 20, 1]列表中第一个元素为:Optional[10]
列表中任意元素:Optional[10]
[10, 12, 9, 8, 20, 1]列表中元素总数为6
[10, 12, 9, 8, 20, 1]列表中最大值为Optional[20]
[10, 12, 9, 8, 20, 1]列表中最小值为Optional[1]

实例中,我们观察到findFirst()findAny()max()等方法的返回值类型为Optional类型,关于这个Optional类,我们将在下一小节具体介绍。

6.2 归约

关于归约的终止操作,有下面几个常用方法:

  • reduce(T identity, BinaryOperator b):可以将流中的元素反复结合起来,得到一个值。返回 T;

  • reduce(BinaryOperator b):可以将流中的元素反复结合起来,得到一个值,返回 Optional<T>

归约相关方法的使用实例如下:

import java.util.Arrays;
import java.util.List;
import java.util.Optional;public class StreamDemo9 {public static void main(String[] args) {// 创建一个整型列表List<Integer> integers = Arrays.asList(10, 12, 9, 8, 20, 1);// 使用 reduce(T identity, BinaryOperator b) 计算列表中所有整数和Integer sum = integers.stream().reduce(0, Integer::sum);System.out.println(sum);// 使用 reduce(BinaryOperator b) 计算列表中所有整数和,返回一个 Optional<T>Optional<Integer> reduce = integers.stream().reduce(Integer::sum);System.out.println(reduce);}}

运行结果:

60
Optional[60]

6.3 收集

collect(Collector c):将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。

实例如下:

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;public class StreamDemo10 {public static void main(String[] args) {// 创建一个整型列表List<Integer> integers = Arrays.asList(10, 12, 9, 8, 20, 1, 10);Set<Integer> collect = integers.stream().collect(Collectors.toSet());System.out.println(collect);}}

运行结果:

[1, 20, 8, 9, 10, 12]

Collector 接口中的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。java.util.stream.Collectors 类提供了很多静态方法,可以方便地创建常用收集器实例,常用静态方法如下:

  • static List<T> toList():把流中元素收集到List

  • static Set<T> toSet():把流中元素收集到Set

  • static Collection<T> toCollection():把流中元素收集到创建的集合。

7. 小结

通过本小节的学习,我们知道了Stream不同于java.io下的输入输出流,它主要用于处理数据。Stream API可用于处理非关系型数据库中的数据;想要使用流式操作,就要知道创建Stream对象的几种方式;流式操作可分为创建Stream对象、中间操作和终止操作三个步骤。多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。执行终止操作会从流的流水线上生成结果,其结果可以是任何不是流的值。

这篇关于45. 【Java教程】流式操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1072626

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听