Java大联盟
致力于最高效的Java学习
关注
B 站搜索:楠哥教你学Java
获取更多优质视频教程
大家好,我是楠哥,今天给大家分享一下 Java 中的 Stream 流,关于这个技术点其实大家可能或多或少都了解过一些,毕竟你肯定背过面经,JDK 新特性你应该了解过。
但是大部分的小伙伴可能也仅仅停留在背诵的程度,在实际开发中可能并没有去使用,其实是挺浪费的,因为 Stream 流功能非常强大,学会使用 Stream ,可以极大地提升你写代码的效率,让代码变得非常简洁。
所以今天专门写这篇教程,给大家讲解一下Stream流,希望能够帮助大家写出更高效、更漂亮的代码。
Stream 流是 Java 8 发布的对集合进行操作的新特性,主要有3个概念:数据源、数据处理、收集结果,我们使用 Stream 只需要搞清楚这三个概念就 OK 了,非常的简单。
1、数据源就是指你要操作的集合数据
2、数据处理就是你要对集合进行的各种操作
3、收集结果就是把操作之后的结果进行相应的处理
那么首先我会通过一个例子给大家直观地展示 Stream 流的强大之处。
首先定义一个实体类。
代码语言:javascript复制@Data
@AllArgsConstructor
class User{
private String name;
private Integer age;
private Integer salary;
}
然后对该实体类进行操作,创建一个集合,定义 3 个 User 对象,添加到集合中。
代码语言:javascript复制List<User> list = Arrays.asList(
new User("小明",20,3000),
new User("小红", 20,2000),
new User("小张", 21,3000)
);
接下来我们要对集合进行筛选,比如选出 age < 21 的,那么代码如下所示。
代码语言:javascript复制List<User> newList = new ArrayList<>();
for (User user : list) {
if(user.getAge()>21)newList.add(user);
}
非常简单,这个时候新的需求来了,选出 salary > 2000 的,也很简单,代码如下所示。
代码语言:javascript复制List<User> newList = new ArrayList<>();
for (User user : list) {
if(user.getSalary()>2000)newList.add(user);
}
那么问题来了,如果再来新的需求怎么办?我们观察一下上面的代码,每新增一个筛选条件,我们就需要遍历集合写逻辑,而且整段代码除了判断条件不同之外,其他的代码完全重复,这样的代码复用性太低了,那么我使用 Stream 流就可以非常简洁的完成上述代码。
直接上代码,先定义两个 Predicate 对象作为条件,然后直接用 Stream 进行条件筛选即可,代码如下所示。
代码语言:javascript复制Predicate<User> predicate1 = user -> user.getAge()<21;
Predicate<User> predicate2 = user -> user.getSalary()>2000;
System.out.println(list.stream()
.filter(predicate1)
.filter(predicate2)
.collect(Collectors.toList()));
上述代码还可以再简化,如下所示。
代码语言:javascript复制System.out.println(list.stream()
.filter(user -> user.getAge() < 21)
.filter(user -> user.getSalary() > 2000)
.collect(Collectors.toList()));
这种形式其实相当于两个条件叠加起来,是一个且关系,我们也可以展开来写,如下所示。
代码语言:javascript复制System.out.println(list.stream()
.filter(predicate1.and(predicate2))
.collect(Collectors.toList()));
同理,如果要筛选或关系,直接使用 or 方法即可,如下所示。
代码语言:javascript复制System.out.println(list.stream()
.filter(predicate1.or(predicate2))
.collect(Collectors.toList()));
这样的话,代码就变得非常简洁,即使要增加新的逻辑,也只需要把最核心的业务代码加上就可以了,而不用去写重复冗余的代码,极大的提升了写代码的效率。
好了,了解完 Stream 的基本操作之后,我们接下来详细学习 Stream 流的使用,要使用 Stream 流,我们首先应该学会 Lambda 表达,这是基础,那么什么是 Lambda 表达式?
Lambda 表达式允许开发者将函数作为参数传给某个方法,即支持函数式编程,这并不是一种新技术,很多基于 JVM 的语言如 Groovy 和 Scala 都支持函数式编程,Java 官方直到 Java8 才引入函数式编程机制。在 Java8 诞生之前,开发者更多的关注点在于对象的属性,这也是面向对象编程思想的核心,即对数据进行抽象,而函数式编程则是对行为进行抽象,是面向函数进行编程。
Lamdba 表达式说简单一点,就是为了简化接口的编程方式,通过一个例子来直观理解什么是 Lambda 表达式:启动一个线程,不使用 Lambda 表达式的代码如下所示。
代码语言:javascript复制new Thread(new Runnable() {
@Override
public void run() {
System.out.println("do it..."");
}
}).start();
使用 Lambda 表达式的代码如下所示。
代码语言:javascript复制new Thread(()-> System.out.println("do it...")).start();
new Thread() 中传递的就是一个函数,它定义了线程的具体任务,即行为。通过对比可以得出结论,使用 Lambda 表达式可以替代传统的匿名类开发方式,不需要创建匿名类即可完成业务逻辑的代码编写,开发效率更高。
Lambda 表达式由 3 部分组成:1、参数。2、->。3、函数主体,如下图所示。
这里需要注意,能够使用 Lambda 表达式的必须是一个函数接口,函数接口是指该接口中只包含一个方法,如 Runnable 接口。
代码语言:javascript复制@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
如果一个接口中包含超过两个方法,则不能使用 Lambda 表达式,好了,学会了 Lambda 表达式,现在我们回到 Stream 流的使用。
Java 8 针对数据处理提供了 Stream API,让开发者能够以声明的方式来处理数据,Stream 对数据的处理类似于 SQL 语句查询数据库,将数据集合抽象成一种流,提供传输流的管道,并且可以在管道的节点上添加处理,如过滤、排序等,常用方法如下所示。
filter 过滤
filter() 方法是 Stream 提供的对数据进行过滤的 API,需要结合 Lambda 表达式来处理,比如过滤出目标集合中长度大于等于 5 的字符串,具体操作如下所示。
代码语言:javascript复制List<String> list = Arrays.asList("Hello","World","Java");
list.stream().filter(str->str.length()>=5).forEach(str-> System.out.println(str));
运行结果如下图所示。
Predicate 多条件过滤
如果需要通过多个条件对集合进行过滤,可以使用 Predicate 来处理,Predicate 可以定义具体的过滤条件,调用多次 filter() 方法,通过传入不同的 Predicate 对象来进行过滤,具体操作如下所示。
代码语言:javascript复制List<String> list = Arrays.asList("Hello","World","Java");
Predicate<String> predicate1 = str->str.length()>=5;
Predicate<String> predicate2 = str->str.startsWith("H");
list.stream()
.filter(predicate1)
.filter(predicate2)
.forEach(str-> System.out.println(str));
运行结果如下图所示。
除了上述方法,也可以调用 Predicate 对象的 and() 方法,对多个 Predicate 对象进行且运算,或者用 or() 进行或运算,如下所示。
代码语言:javascript复制List<String> list = Arrays.asList("Hello","World","Java");
Predicate<String> predicate1 = str->str.length()>=5;
Predicate<String> predicate2 = str->str.startsWith("H");
list.stream()
.filter(predicate1.and(predicate2))
.forEach(str-> System.out.println(str));
运行结果如下图所示。
同时也可将 Predicate 作为参数传递给目标方法,具体操作如下所示。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
List<String> list = Arrays.asList("Hello","World","Java");
//输出以J开头的字符串
test(list,(str)->str.startsWith("J"));
//输出以a结尾的字符串
test(list,(str)->str.endsWith("a"));
//输出长度大于等于5的字符串
test(list,(str)->str.length()>=5);
//输出全部字符串
test(list,(str)->true);
//不输出
test(list,(str)->false);
}
public static void test(List<String> list, Predicate<String> predicate){
for(String str:list){
if(predicate.test(str)){
System.out.println(str);
}
}
}
}
运行结果如下图所示。
limit 截取
使用 limit() 方法可以对数据集合进行截取,原理与 SQL 语句的 limit 一致,具体操作如下所示。
代码语言:javascript复制List<String> list = Arrays.asList("Hello","World","Java");
list.stream()
.limit(2)
.forEach(str-> System.out.println(str));
运行结果如下图所示。
limit() 也可以结合 filter() 来使用,如下所示。
代码语言:javascript复制List<String> list = Arrays.asList("Hello","World","Java");
list.stream()
.filter(str->str.length()>=5)
.limit(1)
.forEach(str-> System.out.println(str));
运行结果如下图所示。
sorted 排序
使用 sorted() 方法可以对目标集合的数据进行排序,如下所示。
代码语言:javascript复制List<Integer> list = Arrays.asList(1,6,2,3,5,4);
list.stream()
.sorted()
.forEach(num-> System.out.println(num));
运行结果如下图所示。
默认是升序排列,可通过添加 Comparator.reverseOrder() 进行降序排列,如下所示。
代码语言:javascript复制List<Integer> list = Arrays.asList(1,6,2,3,5,4);
list.stream()
.sorted(Comparator.reverseOrder())
.forEach(num-> System.out.println(num));
运行结果如下图所示。
max 返回集合最大值,min 返回集合最小值
需要注意的是 max() 和 min() 的返回值是 Optional 类型,Optional 也是 Java8 提供的新特性,Optional 类是一个可以为 null 的容器对象,需要调用 get() 方法取出容器内的数据,具体操作如下所示。
代码语言:javascript复制List<Integer> list = Arrays.asList(1,6,2,3,5,4);
System.out.println(list.stream().max(Integer::compareTo).get());
运行结果如下图所示。
代码语言:javascript复制List<Integer> list = Arrays.asList(1,6,2,3,5,4);
System.out.println(list.stream().min(Integer::compareTo).get());
运行结果如下图所示。
map 对集合中元素进行特定操作
如集合中的每个元素 10 之后输出,具体操作如下所示。
代码语言:javascript复制List<Integer> list = Arrays.asList(1,6,2,3,5,4);
list.stream()
.map(num->num 10)
.forEach(num-> System.out.println(num));
运行结果如下图所示。
reduce 对集合中元素进行特定操作
reduce() 和 map() 一样,都可以对集合中元素进行操作,区别在于 reduce() 是将所有元素按照传入的逻辑进行处理,并将结果合并成一个值返回,如返回集合所有元素之和,操作如下所示。
代码语言:javascript复制List<Integer> list = Arrays.asList(1,6,2,3,5,4);
System.out.println(list.stream().reduce((sum,num)->sum num).get());
需要注意的是 reduce() 的返回值是 Optional 类型,需要调用 get() 方法取出容器内的数据,运行结果如下图所示。
collection 基于目标集合的元素生成新集合
从目标集合中取出所有的奇数生成一个新的集合,具体操作如下所示。
代码语言:javascript复制List<Integer> list = Arrays.asList(1,6,2,3,5,4);
List<Integer> list2 = list.stream()
.filter(num->num%2!=0)
.collect(Collectors.toList());
list2.forEach(num-> System.out.println(num));
运行结果如下图所示。