Stream API
Java 8 引入了 Stream API,它提供了一种高效且易于使用的处理数据的方式。Stream API 可以以声明式的方式处理数据集合(例如列表或数组)。Stream 操作可以是串行的也可以是并行的,这使得它可以很容易地利用多核架构。
特点
1、Stream 自己不会存储数据。
2、Stream 不会改变源对象。相反,它们会返回一个持有结果的新Stream对象
3、Stream 操作时延迟执行的。这就意味着它们等到有结果时候才会执行
关键概念
Stream:一个流是一系列元素的序列,支持顺序和并行处理。
Intermediate Operations(中间操作):返回一个新的流。这些操作通常允许延迟执行,并且可以链式调用。
Terminal Operations(终端操作):触发实际的计算,产生结果或副作用。一旦执行了终端操作,流就被消费并且不能再被使用。
Stream操作 | ||
---|---|---|
中间操作(Intermediate Operations) | 无状态(Stateless) | filter(), map(), mapToInt(), mapToLong(), mapToDouble(), flatMap(), flatMapToInt(), flatMapToLong(), flatMapToDouble(), peek(), eunordered() |
有状态(Stateful) | distinct(), sorted(), sorted(Comparator comparator), limit(), skip() | |
终端操作(Terminal Operations) | 非短路操作(Unshort-circuiting) | forEach(), forEachOrdered(), toArray(), reduce(), collect(), max(), min(), count() |
短路操作(Short-circuiting) | anyMatch(), allMatch(), noneMatch(), findFirst(), findAny() |
创建流
通过 java.util.Collection.stream()
方法用集合创建流
List<String> list = Arrays.asList("d","a", "b", "c");
// 顺序流
Stream<String> streamList = list.stream();
streamList.forEach(System.out::print);
// 并行流(效率高,线程不安全)
Stream<String> parallelStream = list.parallelStream();
parallelStream.forEach(System.out::print);
使用 java.util.Arrays.stream(T[] array)
方法用数组创建流
int[] array = {1,3,5,6,8};
IntStream streamint = Arrays.stream(array);
streamint.forEach(System.out::print);
使用 Stream
的静态方法: of()
、iterate()
、generate()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
stream.forEach(System.out::print);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::print);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::print);
中间操作
操作 | 描述 |
---|---|
limit(n) | 从 list 中取出 n 个元素 |
skip(n) | 从 list 第 n 个元素开始,取出所有元素 |
distinct() | 去除重复 |
filter(Predicate<? super T> predicate) | 筛选条件 |
map(Function mapper) | 将元素转换成其他形式或提取信息 |
sorted() | 对元素进行排序 |
limit(n) & skip(n) & distinct()
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰","张无忌");
}
list.stream().limit(2).forEach(System.out::println); //张无忌 周芷若
list.stream().skip(3).forEach(System.out::println);// 张强 张三丰 张无忌
list.stream().distinct().forEach(System.out::println); //张无忌 周芷若 赵敏 张强 张三丰
过滤
filter(Predicate<? super T> predicate)
- 括号中的内容为一个断言型函数式接口,其中的 T 为当前集合中元素的类型。
使用 Stream API 来过滤出年龄大于20岁的用户,并打印这些用户的信息。
class User {
private String name;
private int age;
private String country;
private char gender;
public User(String name, int age, String country, char gender) {
this.name = name;
this.age = age;
this.country = country;
this.gender = gender;
}
public int getAge() { return age; }
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", country='" + country + '\'' +
", gender=" + (gender == 'M' ? "Male" : "Female") +
'}';
}
}
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User("欧阳雪",18,"中国",'F'));
userList.add(new User("Tom",24,"美国",'M'));
userList.add(new User("Harley",22,"英国",'F'));
userList.add(new User("向天笑",20,"中国",'M'));
userList.add(new User("李康",22,"中国",'M'));
userList.add(new User("小梅",20,"中国",'F'));
userList.add(new User("何雪",21,"中国",'F'));
userList.add(new User("李康",22,"中国",'M'));
}
userList.stream()
.filter((user)->user.getAge()>20)
.forEach(System.out::println);
输出
User{name='Tom', age=24, country='美国', gender=Male}
User{name='Harley', age=22, country='英国', gender=Female}
User{name='李康', age=22, country='中国', gender=Male}
User{name='李康', age=22, country='中国', gender=Male}
映射
map(Function mapper)
从用户列表中提取出所有用户的年龄,并将这些年龄逐一打印出来。
userList.stream()
.map((user -> user.getAge()))
.forEach(System.out::println);
排序
sorted()
userList.stream()
.filter((user) -> user.getAge() > 20) // 过滤出年龄大于20岁的用户
.sorted((o1, o2) -> o1.getAge() - o2.getAge()) // 按年龄升序排序
.forEach(System.out::println); // 打印每个用户的信息
User{name='何雪', age=21, country='中国', gender=Female}
User{name='Harley', age=22, country='英国', gender=Female}
User{name='李康', age=22, country='中国', gender=Male}
User{name='李康', age=22, country='中国', gender=Male}
User{name='Tom', age=24, country='美国', gender=Male}
终端操作
操作 | 描述 | 备注 |
---|---|---|
forEach() | 遍历流中每一个元素,会关闭流 | forEach 不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环 |
findFirst() | 返回流中的第一个元素 | 在顺序流中,它总是返回第一个元素;在并行流中,它也会尝试找到第一个元素,但可能会有性能开销,因为它需要协调各个线程的结果。 |
findAny() | 返回流中的任意一个元素 | 在顺序流中,它通常返回第一个元素;在并行流中,它会从任何可用的线程中返回一个元素,这可以提高性能,因为不需要等待所有线程完成。 |
List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date");
// 使用 findFirst() 获取第一个元素
Optional<String> first = list.stream()
.filter(s -> s.startsWith("B"))
.findFirst();
// 输出结果
first.ifPresent(System.out::println); // 输出: Banana
List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date");
// 使用 findAny() 获取任意一个元素
Optional<String> any = list.parallelStream() // 使用并行流
.filter(s -> s.length() > 5)
.findAny();
// 输出结果
any.ifPresent(System.out::println); // 可能输出: Banana, Cherry 或其他符合条件的元素
注意事项
- 在顺序流中,
findFirst()
和findAny()
通常会有相同的行为,即都返回第一个符合条件的元素。 - 在并行流中,
findFirst()
可能会有更高的性能开销,因为它需要确保返回的是第一个元素,而findAny()
则更灵活,可以从任意一个线程中返回一个元素,从而可能提高性能。
操作 | 描述 |
---|---|
allMatch() | 要求 Stream 中所有元素都满足条件才返回 true |
anyMatch() | 只要有1个元素满足就返回 true |
noneMatch() | 要求所有的元素都不满足条件才返回 true |
//每个学生年纪都为18才返回true
boolean flag = list.stream().allMatch(e -> e.getAge() == 18);
//任意一个学生是18,则返回true
boolean flag = list.stream().anyMatch(e -> e.getAge() == 18);
//学生年纪没有一个是18 ,则返回true
boolean flag = list.stream().noneMatch(e -> e.getAge() == 18);
操作 | 描述 |
---|---|
reduce | 将整个数据流的值规约为一个值,其中count、min、max底层就是使用reduce,也可用于字符串连接 |
min | 最值操作 需要自定义比较器,返回数据流中最小的值 |
max | 最值操作 需要自定义比较器,返回数据流中最大的值 |
count | 返回一个 long 类型的值,表示流中的元素数量 |
toArray | 数组操作 将数据流的元素转换成数组 |
reduce
签名:Optional<T> reduce(BinaryOperator<T> accumulator)
- 不需要提供初始值。从流中的第一个元素开逐步应用二元操作
accumulator
,直到处理完所有元素 - 如果流为空,则返回
Optional.empty()
;否则,返回一个包含结果的Optional<T>
签名:T reduce(T identity, BinaryOperator<T> accumulator)
- 需要提供一个初始值
identity
。它会从这个初始值开始,逐步应用二元操作accumulator
来累积结果 - 即使流为空,也会返回初始值
identity
// 字符串连接 concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 集合累加求和 另一写法reduce(0,Integer::sum)
int sum2 = Stream.of(1, 2, 3, 4, 5).reduce((t, u) -> t + u).get();
// 输出最大值 另一写法reduce(Integer::max)
int max = Stream.of(1, 2, 3, 4, 5).reduce((t, u) -> t >= u ? t : u).get();
int sum3 = Stream.of(1, 2, 3, 4, 5).reduce(0, (t, u) -> t + u);
第 2 行、
.reduce("", String::concat)
:将字符串连接起来。初始值是空字符串
String::concat
是一个方法引用,它接受两个字符串参数并返回它们的连接结果
第 4 行、
.reduce((t, u) -> t + u)
:对流中的所有整数进行累加。此处未提供初始值,返回Optional<Integer>
.get()
:从Optional<Integer>
中获取实际的整数值
第 6 行、
.reduce((t, u) -> t >= u ? t : u)
:找出流中的最大值。此处未提供初始值,返回Optional<Integer>
.get()
:从Optional<Integer>
中获取实际的整数值
第 8 行、
.reduce(0, (t, u) -> t + u)
:使用reduce
方法对流中的所有整数进行累加。这里提供了初始值0
,并且BinaryOperator
是(t, u) -> t + u
,表示将当前值t
和下一个值u
相加。
min、max
最值操作 需要自定义比较器,返回数据流中最大、最小的值
//查年纪最大的
Optional<Student> max =
list.stream().max(Comparator.comparing(Student::getAge));
if (max.isPresent()) {
Student student = max.get();
}
toArray
数组操作 将数据流的元素转换成数组
List<String> list = Arrays.asList("a","b","c","a");
//返回数组
String[] newArr = list.stream().toArray(String[]::new);
将 Stream 转换为 list
语法:Stream 对象名.collect(Collectors.toList());
// 使用Stream API过滤出年龄大于20岁的学生
List<Student> list1 = list.stream()
.filter(t -> t.getAge() > 20)
.collect(Collectors.toList());
// 打印过滤后的学生列表
list1.forEach(System.out::println);