这篇文章写Stream API 的使用,各个用例中会大量结合 lambda
表达式,如果 函数式接口
和 lambda表达式
玩得不熟的朋友先看看 01-函数式接口和 lambda 表达式 ,如果对 Stream API 的创建、中间操作、终止操作相关接口不熟悉的朋友先看看 05-Stream API 第一篇。
提示: 表格的排版在手机端阅读可能有点乱,表格内容可以尝试手机横屏阅读或者电脑端阅读。
Java常用的内置函数式接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 | T | void | 对给定的 T 类型的参数,进行操作, 核心方法: void accept(T t); |
Supplier 供给型接口 | 空参 | T | 获取一个 T 类型的对象 核心方法: T get(); |
Function<T, R> 函数型接口 | T | R | 对 T 类型的参数进行操作,返回 R 类型的结果 核心方法: R apply(T t); |
Predicate 断定型接口 | T | boolean | 判断 T 类型的参数,是否满足某约束,返回 boolean 值 核心方法: boolean test(T t); |
BiFunction<T, U, R> | T, U | R | 对类型为 T, U 参数应用操作, 返回 R 类型的结果。 核心方法为 R apply(T t, U u); |
UnaryOperator (Function子接口) |
T | T | 对类型为T的对象进行一元运算, 并返回T类型的结果。 核心方法为 T apply(T t); |
BinaryOperator (BiFunction 子接口) |
T, T | T | 对类型为T的对象进行二元运算, 并返回T类型的结果。 核心方法为 T apply(T t1, T t2); |
BiConsumer<T, U> | T, U | void | 对类型为T, U 参数应用操作。 核心方法为 void accept(T t, U u) |
ToIntFunction ToLongFunction ToDoubleFunction |
T | int/long/double | T 类型的参数, 返回int\long\double 类型的结果, 核心方法为 int\long\double applyAsInt(T value); |
IntFunction LongFunction DoubleFunction |
int/long/double | R | 参数分别为int、long、double 类型的函数, 返回 R 类型的结果, 核心方法: R apply(int\long\double value); |
1 Stream 创建 API
Stream创建:可通过 数组、Collction 和 Steam类的静态方法来创建一个Stream实例。
定义 Person ,在下面大部分测试用例用到的实体 Bean:
public class Person {
//姓名
private String name;
//年龄
private int age;
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return getAge() == person.getAge() &&
Objects.equals(getName(), person.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge());
}
}
1.1 通过数组创建
/**
* 通过数组创建Stream
*/
@Test
public void testCreateByArray() {
int[] nums = new int[]{0, 1, 5, 2, 4, 8, 9};
Person[] personArray = new Person[]{
new Person("张三", 24),
new Person("李四", 29),
new Person("王五", 22),
new Person("赵六", 15)
};
// Arrays.stream(T[] array) 将指定的数组作为数据源,返回一个顺序流
IntStream stream = Arrays.stream(nums);
Stream<Person> personStream = Arrays.stream(personArray);
//打印 stream 中的元素
System.out.println("打印 stream 中的元素: ");
stream.forEach(item -> System.out.print(item + " "));
//打印 personStream 中的元素
System.out.println("\n打印 personStream 中的元素: ");
personStream.forEach(item -> System.out.println(item + " "));
}
运行结果:
打印 stream 中的元素:
0 1 5 2 4 8 9
打印 personStream 中的元素:
Person{name='张三', age=24}
Person{name='李四', age=29}
Person{name='王五', age=22}
Person{name='赵六', age=15}
1.2 通过Collection创建
Collection接口中提供了 stream() 获取顺序流和 parallelStream() 获取并行流的两个方法
顺序流:串行流,流中的元素序列顺序和元素在数据源中的顺序是一样的。
并行流:把元素序列分成多个数据块,并用不同的线程分别处理每个数据块的流
Stream API 可以通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。
public class StreamAPITest {
List<Person> persons = null;
@Before
public void before() {
persons = Arrays.asList(
new Person("刘一", 25),
new Person("陈二", 12),
new Person("张三", 24),
new Person("李四", 29),
new Person("王五", 22),
new Person("赵六", 15),
new Person("孙七", 16),
new Person("周八", 18)
);
}
/**
* 通过Collection创建Stream
*/
@Test
public void testCreateByCollection() {
//stream() 将此集合作为数据源,返回一个顺序流
Stream<Person> seqStream = persons.stream();
//parallelStream() 将此集合作为数据源,返回一个并行流
Stream<Person> parallelStream = persons.parallelStream();
//打印顺序流 seqStream 中的元素
System.out.println("打印顺序流 seqStream 中的元素: ");
seqStream.forEach(item -> System.out.println("线程名" + Thread.currentThread().getName() + "--" + item + " "));
//并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流
// 打印并行流 parallelStream 中的元素,并行输出,输出结果是无序的
System.out.println("\n打印并行流 parallelStream 中的元素,并行输出,输出结果是无序的: ");
parallelStream.forEach(item -> System.out.println("线程名" + Thread.currentThread().getName() + "--" + item + " "));
System.out.println("\nsequential()把并行流切换成顺序流,输出: ");
persons.parallelStream()
.sequential()//并行流切换成顺序流
.forEach(item -> System.out.println("线程名" + Thread.currentThread().getName() + "--" + item + " "));
}
}
运行结果:
打印顺序流 seqStream 中的元素:
线程名main--Person{name='刘一', age=25}
线程名main--Person{name='陈二', age=12}
线程名main--Person{name='张三', age=24}
线程名main--Person{name='李四', age=29}
线程名main--Person{name='王五', age=22}
线程名main--Person{name='赵六', age=15}
线程名main--Person{name='孙七', age=16}
线程名main--Person{name='周八', age=18}
打印并行流 parallelStream 中的元素,并行输出,输出结果是无序的:
线程名main--Person{name='赵六', age=15}
线程名main--Person{name='王五', age=22}
线程名main--Person{name='周八', age=18}
线程名main--Person{name='孙七', age=16}
线程名ForkJoinPool.commonPool-worker-11--Person{name='李四', age=29}
线程名ForkJoinPool.commonPool-worker-9--Person{name='张三', age=24}
线程名ForkJoinPool.commonPool-worker-2--Person{name='陈二', age=12}
线程名ForkJoinPool.commonPool-worker-9--Person{name='刘一', age=25}
sequential()把并行流切换成顺序流,输出:
线程名main--Person{name='刘一', age=25}
线程名main--Person{name='陈二', age=12}
线程名main--Person{name='张三', age=24}
线程名main--Person{name='李四', age=29}
线程名main--Person{name='王五', age=22}
线程名main--Person{name='赵六', age=15}
线程名main--Person{name='孙七', age=16}
线程名main--Person{name='周八', age=18}
1.3 通过Stream的静态方法创建Stream
Stream接口提供了几个静态方法可以创建一个Stream实例,常用的是:of()、iterate()、generate()
/**
* 通过Stream的静态方法创建Stream
*/
@Test
public void testCreateByStaticMethod() {
// 将参数数组作为数据源,返回一个顺序流,底层调用的是Arrays.stream(T[] array)
Stream<Integer> of = Stream.of(0, 1, 5, 2, 4, 8, 9);// 不断产生 0,2,4,6,8,10.,...,n,n+2
System.out.println("of() 将参数数组作为数据源,返回一个顺序流: ");
of.forEach(item -> System.out.print(item + " "));
// 创建无限流,入参是初始元素和UnaryOperator函数式接口,返回一个有规律的无限顺序流
Stream<Integer> iterate = Stream.iterate(0, n -> n + 2);// 不断产生 0,2,4,6,8,10.,...,n,n+2
System.out.println("\n\niterate() 返回一个有规律的无限顺序流,限制输出前10个: ");
iterate.limit(10).forEach(item -> System.out.print(item + " "));
// 创建无限流,入参是Supplier,返回一个无规律的无限顺序流,其中每个元素由提供的Supplier生成,适用于生成恒定流、随机元素流
Stream<Integer> generate = Stream.generate(() -> (int) (Math.random() * 10));// 不断产生 0-9 的随机数
System.out.println("\n\ngenerate() 返回一个有规律的无限顺序流,限制输出前10个: ");
generate.limit(10).forEach(item -> System.out.print(item + " "));
}
运行结果:
of() 将参数数组作为数据源,返回一个顺序流:
0 1 5 2 4 8 9
iterate() 返回一个有规律的无限顺序流,限制输出前10个:
0 2 4 6 8 10 12 14 16 18
generate() 返回一个有规律的无限顺序流,限制输出前10个:
2 6 9 2 7 1 4 6 0 0
2 终止操作
因为 Stream 有惰性求值的特点,中间操作,并不会立即执行,只有触发终止操作时,才会进行实际的运算。
Stream 操作可以有 0个或者多个中间操作
,但是 有且只有一个终止操作
。
所以下面为了方便演示,我们讲解终止操作的使用,再讲解中间操作的使用。
2.1 allMatch
allMatch操作,如果流中所有元素都匹配 predicate 或者是空流,返回true,否则返回false
public class StreamAPITest {
List<Person> persons = null;
@Before
public void before() {
persons = Arrays.asList(
new Person("刘一", 25),
new Person("陈二", 12),
new Person("张三", 24),
new Person("李四", 29),
new Person("王五", 22),
new Person("赵六", 15),
new Person("孙七", 16),
new Person("周八", 18)
);
}
/**
* allMatch操作,如果流中所有元素都匹配 predicate 或者是空流,返回true,否则返回false
*/
@Test
public void testStreamAllMatch() {
Stream<Pe