⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 jianshu.com/p/11c925cdba50 「litesky」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

相信Java8的Stream 大家都已听说过了,但是可能大家不会用或者用的不熟,文章将带大家从零开始使用,循序渐进,带你走向Stream的巅峰。

操作符

什么是操作符呢?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。

Stream的操作符大体上分为两种:中间操作符和终止操作符

中间操作符

对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。

中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):

  • map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。
  • flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。
  • limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。
  • distint 去重操作,对重复元素去重,底层使用了equals方法。
  • filter 过滤操作,把不想要的数据过滤。
  • peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。
  • skip 跳过操作,跳过某些元素。
  • sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。

终止操作符

数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。

  • collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。
  • count 统计操作,统计最终的数据个数。
  • findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。
  • noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
  • min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。
  • reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。
  • forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。
  • toArray 数组操作,将数据流的元素转换成数组。

这里只介绍了Stream,并没有涉及到IntStream、LongStream、DoubleStream,这三个流实现了一些特有的操作符,我将在后续文章中介绍到。

说了这么多,只介绍这些操作符还远远不够;俗话说,实践出真知。那么,Let‘s go。

代码演练

Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。

  • map,可以看到 map 操作符要求输入一个Function的函数是接口实例,功能是将T类型转换成R类型的。

map操作将原来的单词 转换成了每个单的长度,利用了String自身的length()方法,该方法返回类型为int。这里我直接使用了lambda表达式,关于lambda表达式 还请读者们自行了解吧。

public class Main {

public static void main(String[] args) {
Stream.of("apple","banana","orange","waltermaleon","grape")
.map(e->e.length()) //转成单词的长度 int
.forEach(e->System.out.println(e)); //输出
}
}

当然也可以这样,这里使用了成员函数引用,为了便于读者们理解,后续的例子中将使用lambda表达式而非函数引用。

public class Main {

public static void main(String[] args) {
Stream.of("apple","banana","orange","waltermaleon","grape")
.map(String::length) //转成单词的长度 int
.forEach(System.out::println);
}
}

结果如图:

  • mapToInt 将数据流中得元素转成Int,这限定了转换的类型Int,最终产生的流为IntStream,及结果只能转化成int。

public class Main {

public static void main(String[] args) {
Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.mapToInt(e -> e.length()) //转成int
.forEach(e -> System.out.println(e));
}
}

mapToInt如图:

  • mapToLong、mapToDouble 与mapToInt 类似

public class Main {

public static void main(String[] args) {
Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.mapToLong(e -> e.length()) //转成long ,本质上是int 但是存在类型自动转换
.forEach(e -> System.out.println(e));
}
}

mapToLong 如图:

public class Main {

public static void main(String[] args) {
Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.mapToDouble(e -> e.length()) //转成Double ,自动类型转换成Double
.forEach(e -> System.out.println(e));
}
}

mapToDouble如图:

  • flatmap 作用就是将元素拍平拍扁 ,将拍扁的元素重新组成Stream,并将这些Stream 串行合并成一条Stream

public class Main {

public static void main(String[] args) {
Stream.of("a-b-c-d","e-f-i-g-h")
.flatMap(e->Stream.of(e.split("-")))
.forEach(e->System.out.println(e));

}
}

flatmap 如图:

  • flatmapToInt、flatmapToLong、flatmapToDouble 跟flatMap 都类似的,只是类型被限定了,这里就不在举例子了。
  • limit 限制元素的个数,只需传入 long 类型 表示限制的最大数

public class Main {

public static void main(String[] args) {
Stream.of(1,2,3,4,5,6)
.limit(3) //限制三个
.forEach(e->System.out.println(e)); //将输出 前三个 1,2,3
}
}

limit如图:

public class Main {

public static void main(String[] args) {

Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
.distinct() //去重
.forEach(e->System.out.println(e));

}
}

distinct 如图:

  • filter 对某些元素进行过滤,不符合筛选条件的将无法进入流的下游

public class Main {

public static void main(String[] args) {
Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
.filter(e->e>=5) //过滤小于5的
.forEach(e->System.out.println(e));
}
}

filter 如图:

  • peek 挑选 ,将元素挑选出来,可以理解为提前消费

public class Main {

public static void main(String[] args) {

User w = new User("w",10);
User x = new User("x",11);
User y = new User("y",12);

Stream.of(w,x,y)
.peek(e->{e.setName(e.getAge()+e.getName());}) //重新设置名字 变成 年龄+名字
.forEach(e->System.out.println(e.toString()));

}

static class User {

private String name;

private int age;

public User(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 "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

}

peek 如图:

  • skip 跳过 元素

public class Main {

public static void main(String[] args) {
Stream.of(1,2,3,4,5,6,7,8,9)
.skip(4) //跳过前四个
.forEach(e->System.out.println(e)); //输出的结果应该只有5,6,7,8,9
}
}

skip 如图:

  • sorted 排序 底层依赖Comparable 实现,也可以提供自定义比较器

这里Integer 实现了比较器

public class Main {

public static void main(String[] args) {
Stream.of(2,1,3,6,4,9,6,8,0)
.sorted()
.forEach(e->System.out.println(e));
}
}

sorted 默认比较器如图:

这里使用自定义比较,当然User 可以实现Comparable 接口

public class Main {

public static void main(String[] args) {

User x = new User("x",11);
User y = new User("y",12);
User w = new User("w",10);

Stream.of(w,x,y)
.sorted((e1,e2)->e1.age>e2.age?1:e1.age==e2.age?0:-1)
.forEach(e->System.out.println(e.toString()));

}

static class User {

private String name;

private int age;

public User(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 "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

}

如图:

  • collect 收集,使用系统提供的收集器可以将最终的数据流收集到List,Set,Map等容器中。

这里我使用collect 将元素收集到一个set中

public class Main {

public static void main(String[] args) {
Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.collect(Collectors.toSet()) //set 容器
.forEach(e -> System.out.println(e));
}
}

咦?,不是说终止操作符只能使用一次吗,为什么这里调用了forEach 呢?forEach不仅仅是是Stream 中得操作符还是各种集合中得一个语法糖,不信咋们试试。

public class Main {

public static void main(String[] args) {

Set<String> stringSet = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.collect(Collectors.toSet()); //收集的结果就是set
stringSet.forEach(e->System.out.println(e)); set的语法糖forEach
}

结果如图:

  • count 统计数据流中的元素个数,返回的是long 类型

public class Main {

public static void main(String[] args) {

long count = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.count();

System.out.println(count);
}
}

count 如图:

  • findFirst 获取流中的第一个元素

这里找到第一个元素 apple

public class FindFirst {

public static void main(String[] args) {
Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.findFirst();
stringOptional.ifPresent(e->System.out.println(e));
}
}

findFirst 结果如图:

  • findAny 获取流中任意一个元素

public class FindAny {

public static void main(String[] args) {
Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.parallel()
.findAny(); //在并行流下每次返回的结果可能一样也可能不一样
stringOptional.ifPresent(e->System.out.println(e));
}
}

findAny 在并行流下 使用结果:

输出了orange

输出了banana

  • noneMatch 数据流中得没有一个元素与条件匹配的

这里 的作用是是判断数据流中 一个都没有与aa 相等元素 ,但是流中存在 aa ,所以最终结果应该是false

public class NoneMatch {

public static void main(String[] args) {
boolean result = Stream.of("aa","bb","cc","aa")
.noneMatch(e->e.equals("aa"));
System.out.println(result);
}
}

noneMatch 如图:

  • allMatch和anyMatch 一个是全匹配,一个是任意匹配 和noneMatch 类似,这里就不在举例了。
  • min 最小的一个,传入比较器,也可能没有(如果数据流为空)

public class Main {

public static void main(String[] args) {

Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
.min((e1,e2)->e1.compareTo(e2));

integerOptional.ifPresent(e->System.out.println(e));

}

min如图:

  • max 元素中最大的,需要传入比较器,也可能没有(流为Empty时)

public class Main {

public static void main(String[] args) {

Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
.max((e1,e2)->e1.compareTo(e2));

integerOptional.ifPresent(e->System.out.println(e));

}
}

max 如图:

  • reduce 是一个规约操作,所有的元素归约成一个,比如对所有元素求和,乘啊等。

这里实现了一个加法,指定了初始化的值

public class Main {
public static void main(String[] args) {

int sum = Stream.of(0,9,8,4,5,6,-1)
.reduce(0,(e1,e2)->e1+e2);
System.out.println(sum);
}
}

reduce 如图:

  • forEach

forEach 其实前就已经见过了,对每个数据遍历迭代

  • forEachOrdered 适用用于并行流的情况下进行迭代,能保证迭代的有序性

这里通过并行的方式输出数字

public class ForEachOrdered {
public static void main(String[] args) {
Stream.of(0,2,6,5,4,9,8,-1)
.parallel()
.forEachOrdered(e->{
System.out.println(Thread.currentThread().getName()+": "+e);});
}
}

forEachOrdered 如图:

  • toArray 转成数组,可以提供自定义数组生成器

public class ToArray {
public static void main(String[] args) {
Object[] objects=Stream.of(0,2,6,5,4,9,8,-1)
.toArray();

for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
}
}

toArray 如图:

总结

Java8 Stream就带大家认识到这里,如果你能跟着我的文章把每一个例子都敲一遍,相信都能掌握这些操作符的初步用法。

文章目录
  1. 1. 操作符
    1. 1.1. 中间操作符
    2. 1.2. 终止操作符
  2. 2. 代码演练
  3. 3. 总结