【Java学习笔记十四】——走入Stream API
声明:本文章内容主要摘选自尚硅谷宋红康Java教程、廖雪峰Java教程,示例代码部分出自本人,更多详细内容推荐直接观看以上教程及书籍,若有错误之处请指出,欢迎交流。
一、概念
1.Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
2.应用场景
在需要用到某些操作时(求和、去重、按照要求过滤等),直接使用已经封装好的Stream API进行实现,不需要我们自己写代码实现,Stream API可以极大提高生产力,让我们写出高效率、干净、简洁的代码。
二、创建Stream
- Stream 自己不会存储元素。
- Stream 不会改变源对象,相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味者他们会等到需要结果的时保才执行。
1.通过Stream的of()
//直接用Stream.of()静态方法,传入可变参数即创建了一个能输出确定元素的Stream:
Stream<Integer> stream = Stream.of(2,4,6,8);
//Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。
stream.forEach(System.out::println);
2.通过数组或集合
//基于一个数组或者集合创建,这样该Stream输出的元素就是数组或者集合持有的元素:
//调用Arrays类的 Stream<T> stream(T[] array) :返回一个流
int[] arr1 = {1,2,3,4,5};
IntStream stream1 = Arrays.stream(arr1);
//Stream<E> stream() : 返回一个顺序流
List<String> list = Arrays.asList("AA", "BB", "CC", "DD");
Stream<String> stream2 = list.stream();//通过集合创建
//Stream<E> parallelStream() :返回一个并行流
Stream<String> stream3 = list.parallelStream();
3.通过Supplier(无限流)
创建Stream
还可以通过Stream.generate()
方法,它需要传入一个Supplier
对象:
基于Supplier
创建的Stream
会不断调用Supplier.get()
方法来不断产生下一个元素,这种Stream
保存的不是元素,而是算法,它可以用来表示无限序列。
import java.util.stream.Stream;
import java.util.function.*;
public class StreamTest1{
public static void main(String[] args){
//生成自然数
Stream<Integer> natual = Stream. generate(new NatualSupplier());
//注意:无限序列必须先变成有限序列再打印:
natual.limit(20).forEach(System.out::println);
}
//迭代:遍历前10个偶数(使用Lambda表达式)
Stream.iterate(0, t ->t + 2).limit(10).forEach(System.out::println);
//生成:生成随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
class NatualSupplier implements Supplier<Integer> {
int n = 0;
public Integer get(){
n++;
return n;
}
}
上述代码我们用一个Supplier<Integer>
模拟了一个无限序列(当然受int
范围限制不是真的无限大)。如果用List
表示,即便在int
范围内,也会占用巨大的内存,而Stream
几乎不占用空间,因为每个元素都是实时计算出来的,用的时候再算。
对于无限序列,如果直接调用forEach()
或者count()
这些最终求值操作,会进入死循环,因为永远无法计算完这个序列,所以正确的方法是先把无限序列变成有限序列,例如,用limit()
方法可以截取前面若干个元素,这样就变成了一个有限序列,对这个有限序列调用forEach()
或者count()
操作就没有问题。
三、执行中间操作
1.筛选与切片
public class StreamTest2{
public static void main(String[] args){
List<Employee> employees = EmployeeData.getEmployees();//Employee是我们自己定义的员工类,EmployeeData为数据源,详细请看帖子最底部的代码
//filter(Predicate p) : 接收Lambda, 通过设置的条件从流中过滤出元素。
Stream<Employee> stream1 = employees.stream();
//查询员工表中年龄大于30的员工信息
System.out.println("给定条件筛选元素:");
stream1.filter(e -> e.getAge() > 30).forEach(System.out::println);
//limit(n) : 截断流,使其元素不超过给定数量
System.out.println("截断指定数量的元素");
employees.stream().limit(3).forEach(System.out::println);
//skip(n) :跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
System.out.println("跳过元素:");
employees.stream().skip(3).forEach(System.out::println);
//distinct() :筛选,通过流新生成元素的hashCode()和equals()去除重复元素
System.out.println("去重:");
Stream<String> list2 = Arrays.asList("a","a","b","c","c","d","a").stream();
list2.distinct().forEach(System.out::println);
}
2.映射
Stream.map()
是Stream
最常用的一个转换方法,它把一个Stream
转换为另一个Stream
。
所谓map
操作,就是接收一个函数作为参数,将元素转换为其他形式或提取信息,该函数会被应用到每一个元素上,并将其映射成一个流。例如,对x
计算它的平方,可以使用函数f(x) = x * x
。我们把这个函数映射到一个序列1,2,3,4,5上,就得到了另一个序列1,4,9,16,25:
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s.map(n -> n * n);
flatMap(Function f)
: 接收一个函数作为参数,将流中的每个值都换成另外一个流,然后把所有流连接成一个流
public class StreamTest2{
public static void main(String[] args){
//将一个流嵌入另外一个流,即作为另外一个流的一个元素
Stream<Stream<Character>> streamStream = list.stream().map(StreamTest2::fromStringToStream);
streamStream.forEach(s ->{
s.forEach(System.out::println); //2层遍历方可遍历到作为元素的流的元素(即”AA","BB"等)
});
//使用flatMap(Function f)
Stream<Character> characterStream = list.stream().flatMap(StreamTest2::fromStringToStream);
characterStream.forEach(System.out::println);//直接输出集合中的集合元素的元素,无须再进行2层遍历
//将字符串中的多个字符构成的集合转化为相应的Stream的实例
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list = new ArrayList<>();
for(Character c:str.toCharArray()){
list.add(c);
}
return list.stream();
}
}
}
3.排序
sorted ()
用于对流进行排序
//sorted():自然排序
System.out.println("自然排序:");
List<Integer> list = Arrays.asList(12,99,34,28,55,33);
list.stream().sorted().forEach(System.out::println);
//定制排序
System.out.println("按照员工年龄排序:");
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted((e1, e2)->Integer.compare(e1.getAge(), e2.getAge()))
.forEach(System.out::println);
//上方代码示例中的员工类和员工表
import java.util.ArrayList;
import java.util.List;
class EmployeeData {
public static List<Employee> getEmployees(){
List<Employee> list = new ArrayList<>();
list.add(new Employee(1001,"麻花腾",29));
list.add(new Employee(1002,"赵飞",32));
list.add(new Employee(1003,"林一",40));
list.add(new Employee(1004,"雷布斯",26));
list.add(new Employee(1005,"徐杰",43));
list.add(new Employee(1006,"秦飞",24));
list.add(new Employee(1007,"张三",33));
list.add(new Employee(1008,"何颖琳",24));
return list;
}
}
class Employee {
private int id;
private String name;
private int age;
public Employee(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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 "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
此笔记仅针对有一定编程基础的同学,且本人只记录比较重要的知识点,若想要入门Java可以先行观看相关教程或书籍后再阅读此笔记。
最后附一下相关链接:
Java在线API中文手册
Java platform se8下载
尚硅谷Java教学视频
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!