说起流,我们会想起手机 ,电脑组装流水线,物流仓库商品包装流水线等等。如果把手机 ,电脑,包裹看做最终结果的话,那么加工商品前的各种零部件就可以看做数据源,而中间一系列的加工作业操作,就可以看做流的处理。
 

一、流的概念

Java Se中对于流的操作有输入输出IO流,而Java8中引入的Stream 属于Java API中的一个新成员,它允许你以声明性方式处理数据集合,Stream 使用一种类似 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 注意这里的流操作可以看做是对集合数据的处理。
简单来说,流是一种数据渠道,用于操作数据源(集合、数组、文件等)所生产的元素序列。
  • 源-流会使用一个提供数据的源,如集合、数组或输入|输出资源。
从有序集生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致
  • 元素序列-就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。
  • 数据处理操作-流的数据处理功能支持类似于数据库的操作(数据筛选、过滤、排序等操作)。
  • 流水线-多个流操作本身会返回一个流,多个操作就可以链接起来,成为数据处理的一道流水线。
 

二、流 & 集合

  • 计算的时机
 
集合中数据都是计算完毕的数据,例如从数据库中查询用户记录 按用户id 查询 降序排列 然后通过list 接收用户记录,数据的计算已在放入集合前完成。
流中数据按需计算,按照使用者的需要计算数据,例如通过搜索引擎进行搜索,搜索出来的条目并不是全部呈现出来的,而且先显示最符合的前 10 条或者前 20 条,只有在点击 “下一页” 的时候,才会再输出新的 10 条。流的计算也是这样,当用户需要对应数据时,Stream 才会对其进行计算处理。
  • 外部迭代与内部迭代
 
把集合比作一个工厂的仓库的话,一开始工厂硬件比较落后,要对货物作什么修改,此时工人亲自走进仓库对货物进行处理,有时候还要将处理后的货物转运到另一个仓库中。此时对于开发者来说需要亲自去做迭代,一个个地找到需要的货物,并进行处理,这叫做外部迭代。
当工厂发展起来后,配备了流水线作业,工厂只要根据需求设计出相应的流水线,然后工人只要把货物放到流水线上,就可以等着接收成果了,而且流水线还可以根据要求直接把货物输送到相应的仓库。这就叫做内部迭代,流水线已经帮你把迭代给完成了,你只需要说要干什么就可以了(即设计出合理的流水线)。相当于 Java8 引入的Stream 对数据的处理实现了”自动化”操作。
 

三、流操作过程

整个流操作就是一条流水线,将元素放在流水线上一个个地进行处理。需要注意的是:很多流操作本身就会返回一个流,所以多个操作可以直接连接起来, 如下图这样,操作可以进行链式调用,并且并行流还可以实现数据流并行处理操作。
总的来说,流操作过程分为三个阶段:
  • 创建
借助数据源创建流对象
  • 中间处理
筛选、切片、映射、排序等中间操作
  • 终止流
匹配、汇总、分组等终止操作
 

四、流的创建

对流操作首先要创建对应的流,流的创建集中形式如下:
 

4.1 集合创建流

在 Java 8 中, 集合接口有两个方法来生成流:
  • stream() − 为集合创建串行流。
  • parallelStream() − 为集合创建并行流。
示例代码如下:
public static void main(String[] args) {
/**
* 定义集合l1 并为集合创建串行流
*/
List<String> l1 = Arrays.asList("周星驰", "周杰伦", "周星星", "周润发");
// 返回串行流
l1.stream();
// 返回并行流
l1.parallelStream();
}
上述操作得到的流是通过原始数据转换过来的流,除了这种流创建的基本操作外,对于流的创建还有以下几种方式。
 

4.2 值创建流

//值创建流 生成一个字符串流
Stream<String> stream = Stream.of("java8", "Spring", "SpringCloud");
stream.forEach(System.out::println);

4.3 数组创建流

根据参数的数组类型创建对应的流。
  • Arrays.stream(T[ ])
  • Arrays.stream(int[ ])
  • Arrays.stream(double[ ])
  • Arrays.stream(long[ ])
 /**
* 这里以int 为例 long double 不再举例
*/
Stream stream = Arrays.stream(Arrays.asList(10, 20, 30, 40).toArray());
// 根据数组索引范围创建指定Stream
stream = Arrays.stream(Arrays.asList(10, 20, 30, 40).toArray(), 0, 2);

4.4 文件生成流

stream = Files.lines(Paths.get("C:\\java\\jdbc.properties"));
System.out.println(stream.collect(Collectors.toList()));
// 指定字符集编码
stream = Files.lines(Paths.get("C:\\java\\jdbc.properties"), Charset.forName("utf-8"));
System.out.println(stream.collect(Collectors.toList()));

4.5 函数生成流

两个方法:
  • iterate : 依次对每个新生成的值应用函数
  • generate :接受一个函数,生成一个新的值
 // 重100 开始 生成偶数流
Stream.iterate(100, n -> n + 2);
// 产生1-100 随机数
Stream.generate(() ->(int) (Math.random() * 100 + 1));
 

五、流中间操作

流的中间操作分为三大类:筛选切片、映射、排序。
筛选切片:类似sql 中where 条件判断的意思,对元素进行筛选操作
映射:对元素结果进行转换 ,优点类似select 字段意思或者对元素内容进行转换处理
排序:比较好理解 ,常用sql 中按字段升序 降序操作
 
 
流中间操作数据准备(这里以订单数据处理为例)
@Data
public class Order {
// 订单id
private Integer id;
// 订单用户id
private Integer userId;
// 订单编号
private String orderNo;
// 订单日期
private Date orderDate;
// 收货地址
private String address;
// 创建时间
private Date createDate;
// 更新时间
private Date updateDate;
// 订单状态 0-未支付 1-已支付 2-待发货 3-已发货 4-已接收 5-已完成
private Integer status;
// 是否有效 1-有效订单 0-无效订单
private Integer isValid;
//订单总金额
private Double total;
} Order order01 = new Order(1, 10, "20190301",
new Date(), "上海市-浦东区", new Date(), new Date(), 4, 1, 100.0);
Order order02 = new Order(2, 30, "20190302",
new Date(), "北京市四惠区", new Date(), new Date(), 1, 1, 2000.0);
Order order03 = new Order(3, 20, "20190303",
new Date(), "北京市-朝阳区", new Date(), new Date(), 4, 1, 500.0);
Order order04 = new Order(4, 40, "20190304",
new Date(), "北京市-大兴区", new Date(), new Date(), 4, 1, 256.0);
Order order05 = new Order(5, 40, "20190304",
new Date(), "上海市-松江区", new Date(), new Date(), 4, 1, 1000.0);
ordersList = Arrays.asList(order01, order02, order03, order04, order05);

六、筛选&切片

  • 筛选有效订单

// 过滤有效订单
ordersList.stream().filter((order) -> order.getIsValid() == 1)
  .forEach(System.out::println);
  • 筛选有效订单 取第一页数据(每页2条记录)

 // 过滤有效订单 取第一页数据(每页2条记录) 
ordersList.stream().filter((order) -> order.getIsValid() == 1)
    .limit(2)
    .forEach(System.out::println);
  • 筛选订单集合有效订单 取最后一条记录

// 过滤订单集合有效订单 取最后一条记录
ordersList.stream().filter((order) -> order.getIsValid() == 1)
  .skip(ordersList.size() - 2)  // 跳过前ordersList.size()-2 记录
  .forEach(System.out::println);
  • 筛选有效订单 取第3页数据(每页2条记录)

// 过滤有效订单 取第3页数据(每页2条记录) 并打印到控制台
ordersList.stream().filter((order) -> order.getIsValid() == 1)
  .skip((3 - 1) * 2)
  .limit(2)
  .forEach(System.out::println);
  • 筛选无效订单去除重复订单号记录

// 过滤无效订单 去除重复订单号记录  重写Order equals 与 hashCode 方法
ordersList.stream().filter((order) -> order.getIsValid() == 0)
.distinct()
.forEach(System.out::println);
 

七、映射

  • 过滤有效订单,获取所有订单编号

//过滤有效订单,获取所有订单编号
ordersList.stream().filter((order) -> order.getIsValid() == 1)
  .map((order) -> order.getOrderNo())
  .forEach(System.out::println);
  • 过滤有效订单 ,并分离每个订单下收货地址市区信息

ordersList.stream().map(o -> o.getAddress()
  .split("-"))
  .flatMap(Arrays::stream)
  .forEach(System.out::println);

八、排序

  • 过滤有效订单 根据用户id 进行排序
//过滤有效订单 根据用户id 进行排序
ordersList.stream().filter((order) -> order.getIsValid() == 1)
.sorted((o1, o2) -> o1.getUserId() - o2.getUserId())
.forEach(System.out::println); //或者等价写法
ordersList.stream().filter((order) -> order.getIsValid() == 1)
.sorted(Comparator.comparingInt(Order::getUserId))
.forEach(System.out::println);
  • 过滤有效订单 ,根据订单状态排序 如果订单状态相同根据订单创建时间排序
//过滤有效订单  如果订单状态相同 根据订单创建时间排序 反之根据订单状态排序
ordersList.stream().filter((order) -> order.getIsValid() == 1)
.sorted((o1, o2) -> {
if (o1.getStatus().equals(o2.getStatus())) {
return o1.getCreateDate().compareTo(o2.getCreateDate());
} else {
return o1.getStatus().compareTo(o2.getStatus());
}})
.forEach(System.out::println); // 等价形式
ordersList.stream().filter((order) -> order.getIsValid() == 1)
.sorted(Comparator.comparing(Order::getCreateDate)
.thenComparing(Comparator.comparing(Order::getStatus)))
.forEach(System.out::println);
 
 

九、流的终止操作

终止操作会从流的流水线生成结果。其结果是任何不是流的值,比如常见的List、 Integer,甚 至void等结果。对于流的终止操作,分为以下三类:
 
 

十、查找与匹配

  • 筛选有效订单 匹配是否全部为已支付订单

 // 筛选有效订单 匹配是否全部为已支付订单
System.out.println("allMatch匹配结果:" +
                    ordersList.stream()
                  .filter((order) -> order.getIsValid() == 1)
              .allMatch((o) -> o.getStatus() != 0)
                );
  • 筛选有效订单 匹配是否存在未支付订单

// 筛选有效订单 匹配是否存在未支付订单
System.out.println("anyMatch匹配结果:" +
                    ordersList.stream()
                  .filter((order) -> order.getIsValid() == 1)
                  .anyMatch((o) -> o.getStatus() == 0)
                );
  • 筛选有效订单 全部未完成订单

// 筛选有效订单 全部未完成订单
System.out.println("noneMatch匹配结果:" +
                   ordersList.stream()
                  .filter((order) -> order.getIsValid() == 1)
                  .noneMatch((o) -> o.getStatus() == 5)
                );
  • 筛选有效订单 返回第一条订单

  // 筛选有效订单 返回第一条订单
       System.out.println("findAny匹配结果:"+
                  ordersList.stream()
              .filter((order) -> order.getIsValid() == 1)
              .findAny()
                          .get()
                        );
  • 筛选所有有效订单 返回订单总数

//  筛选所有有效订单 返回订单总数
       System.out.println("count结果:" +
      ordersList.stream()
      .filter((order) -> order.getIsValid() == 1)
      .count()
      );
  • 筛选有效订单 返回金额最大订单金额

// 筛选有效订单 返回金额最大订单金额
       System.out.println("订单金额最大值:" +
                          ordersList.stream()
              .filter((order) -> order.getIsValid() == 1)
              .map(Order::getTotal)
              .max(Double::compare)
              .get()
                        );
  • 筛选有效订单 返回金额最小订单金额

// 筛选有效订单 返回金额最小订单金额
       System.out.println("订单金额最小值:" +
                          ordersList.stream()
              .filter((order) -> order.getIsValid() == 1)
              .map(Order::getTotal)
              .min(Double::compare)
              .get()
                        );

十一、归约&收集

11.1 归约

将流中元素反复结合起来,得到一个值的操作
  • 计算有效订单总金额
// 计算有效订单总金额
System.out.println("有效订单总金额:" +
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.map(Order::getTotal)
.reduce(Double::sum)
.get()
);

11.2 Collector数据收集

将流转换为其他形式,coollect 方法作为终端操作, 接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。最常用的方法,把流中所有元素收集到一个 List, Set 或 Collection中
 

11.3 集合收集

常用集合收集方法 toList、toSet、toCollection、toMap等
  • 筛选所有有效订单 并收集订单列表
// 筛选所有有效订单并收集订单列表
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.collect(Collectors.toList())
.forEach(System.out::println);
  • 筛选所有有效订单并收集订单号与订单金额
// 筛选所有有效订单 并收集订单号 与 订单金额
Map<String,Double> map=ordersList.stream().filter((order) -> order.getIsValid() == 1).
collect(Collectors.toMap(Order::getOrderNo, Order::getTotal));
// java8 下对map进行遍历操作 如果 Map的Key重复,会报错
map.forEach((k,v)->{
System.out.println("k:"+k+":v:"+v);
});
 
 

十二、汇总

汇总操作在Stream流操作比较常见,比如计算总数,求平均等操作,常用方法如下:
 
相关操作如下
  • 筛选所有有效订单 返回订单总数
System.out.println("count结果:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.collect(Collectors.counting())
);
System.out.println("count结果:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.count()
);
  • 返回订单总金额
System.out.println("订单总金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.collect(Collectors.summarizingDouble(Order::getTotal))
);
System.out.println("订单总金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.mapToDouble(Order::getTotal)
.sum()
);
System.out.println("订单总金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.map(Order::getTotal)
.reduce(Double::sum)
.get()
);
  • 返回用户id=20 有效订单平均每笔消费金额
System.out.println("用户id=20 有效订单平均每笔消费金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.filter((order -> order.getUserId()==20))
.collect(Collectors.averagingDouble(Order::getTotal))
);
System.out.println("用户id=20 有效订单平均每笔消费金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.filter((order -> order.getUserId()==20))
.mapToDouble(Order::getTotal)
.average()
.getAsDouble()
); System.out.println("用户id=20 有效订单平均每笔消费金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.filter((order -> order.getUserId()==20))
.collect(Collectors.summarizingDouble(Order::getTotal))
.getAverage()
);
  • 筛选所有有效订单 并计算订单总金额
System.out.println("订单总金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.collect(Collectors.summingDouble(Order::getTotal))
);
 
 

十三、最值

  • 筛选所有有效订单 并计算最小订单金额

System.out.println("最小订单金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.map(Order::getTotal)
.collect(Collectors.minBy(Double::compare))
);
 
  • 筛选所有有效订单 并计算最大订单金额

// 筛选所有有效订单 并计算最大订单金额
System.out.println("最大订单金额:"+
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.map(Order::getTotal)
.collect(Collectors.maxBy(Double::compare))
);

十四、分组&分区

14.1 分组

groupingBy 用于将数据分组,最终返回一个 Map 类型 ,groupingBy 第二参数用于实现多级分组
  • 根据有效订单支付状态进行分组操作
Map<Integer,List<Order>> g01=ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.collect(Collectors.groupingBy(Order::getStatus));
g01.forEach((status,order)->{
System.out.println("----------------");
System.out.println("订单状态:"+status);
order.forEach(System.out::println);
});
  • 筛选有效订单,根据用户id 和 支付状态进行分组
Map<Integer,Map<String,List<Order>>> g02= ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.collect(Collectors.groupingBy(Order::getUserId,
Collectors.groupingBy((o)->{
if(o.getStatus()==0){
return "未支付";
}else if (o.getStatus()==1){
return "已支付";
}else if (o.getStatus()==2){
return "待发货";
}else if (o.getStatus()==3){
return "已发货";
}else if (o.getStatus()==4){
return "已接收";
} else{
return "已完成";
}
}
))
);
g02.forEach((userId,m)->{
System.out.println("用户id:"+userId+"-->有效订单如下:");
m.forEach((status,os)->{
System.out.println("状态:"+status+"---订单列表如下:");
os.forEach(System.out::println);
});
System.out.println("-----------------------");
});

14.2 分区

分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean
  • 分区操作-筛选订单金额>1000 的有效订单
Map<Boolean,List<Order>> g03= ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.collect(Collectors.partitioningBy((o)->o.getTotal()>1000));
g03.forEach((b,os)->{
System.out.println("分区结果:"+b+"--列表结果:");
os.forEach(System.out::println);
});
  • 拼接操作-筛选有效订单并进行拼接
String orderStr=ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.map(Order::getOrderNo)
.collect(Collectors.joining(","));
System.out.println(orderStr);

十五、流的应用

Java8引入Stream流操作,使得对元素的处理更加的方便快捷,通过Stream提供的相关方法很好的结合Lambda、函数式接口、方法引用等相关内容,使得流的处理相比较原始集合处理代码极大简化,Stream支持函数的链式调用,代码上更加紧凑同时Stream支持的元素的并行化处理提高了程序的执行性能。对于Stream流的应用通常在集合元素数据处理上特别是对元素需要进行多次处理的情况,同时对于函数式编程的味道更加浓重,也是以后开发的一个趋势。
 
好了,Java8核心特性之Stream流就介绍到这里了,应该是非常详尽了,希望大家喜欢,多多关注小乐,小乐将会给大家带来更多的技干货。乐字节祝大家端午快乐,学习快乐!

乐字节-Java8核心特性实战之Stream(流)的更多相关文章

  1. 乐字节-Java8核心特性实战之Lambda表达式

    大家好,小乐又来给大家分享Java8核心特性了,上一篇文章是<乐字节|Java8核心实战-接口默认方法>,这次就来讲Java8核心特征之Lambda表达式. Java8 引入Lambda表 ...

  2. 乐字节-Java8核心特性实战-接口默认方法

    JAVA8已经发布很久,是自java5(2004年发布)之后Oracle发布的最重要的一个版本.其中包括语言.编译器.库.工具和JVM等诸多方面的新特性,对于国内外互联网公司来说,Java8是以后技术 ...

  3. 乐字节-Java8核心特性实战之函数式接口

    什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的.从Java8开始引入了函数式接口,其说明比较简单:函数式接口(Functional Interface)就是一个有且仅有一 ...

  4. 乐字节Java8核心特性之方法引用

    大家好,我是乐字节的小乐,上一次我们说到了Java8核心特性之函数式接口,接下来我们继续了解Java8又一核心特性--方法引用. Java8 中引入方法引用新特性,用于简化应用对象方法的调用, 方法引 ...

  5. 乐字节Java8核心特性之Optional类

    大家好啊,上次小乐给大家介绍了Java8最最重要的一个特性——Stream流,点击可以回顾哦. Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在 ...

  6. 乐字节-Java8新特性-接口默认方法

    总概 JAVA8 已经发布很久,而且毫无疑问,java8是自java5(2004年发布)之后的最重要的版本.其中包括语言.编译器.库.工具和JVM等诸多方面的新特性. Java8 新特性列表如下: 接 ...

  7. 乐字节-Java8新特性之Base64和重复注解与类型注解

    上一篇小乐给大家说了<乐字节-Java8新特性之Date API>,接下来小乐继续给大家说一说Java8新特性之Base64和重复注解与类型注解. 一.Base64 在Java 8中,内置 ...

  8. 乐字节-Java8新特性-接口默认方法之Stream流(下)

    接上一篇:<Java8新特性之stream>,下面继续接着讲Stream 5.流的中间操作 常见的流的中间操作,归为以下三大类:筛选和切片流操作.元素映射操作.元素排序操作: 操作 描述 ...

  9. 乐字节-Java8新特性之Stream流(上)

    上一篇文章,小乐给大家介绍了<Java8新特性之方法引用>,下面接下来小乐将会给大家介绍Java8新特性之Stream,称之为流,本篇文章为上半部分. 1.什么是流? Java Se中对于 ...

随机推荐

  1. HDU - 1213 How Many Tables 【并查集】

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1213 题意 给出N个人 M对关系 找出共有几对连通块 思路 并查集 AC代码 #include < ...

  2. 郝健: Linux内存管理学习笔记-第1节课【转】

    本文转载自:https://blog.csdn.net/juS3Ve/article/details/80035751 摘要 MMU与分页机制 内存区域(内存分ZONE) LinuxBuddy分配算法 ...

  3. macd背离的级别

    1分钟的背离可以忽略不看. 5分钟的背离可以预测未来5-6个小时的股价. 15分钟级别的背离可以预测未来24小时之内的股价. 30分钟级别的背离可以做中线. 周线背离可以影响1-2年的股价. 背离级别 ...

  4. 分享知识-快乐自己:SpringMVC 底层执行原理解析

    底层实现原理图: 观看底层代码: 1):打开 web.xml 文件  2):按住 Ctrl + 鼠标左键 进入底层查看源码   3):按住 Ctrl+o 找到对应的方法doDispatch   5): ...

  5. leetcode 67. Add Binary (高精度加法)

    Given two binary strings, return their sum (also a binary string). For example,a = "11"b = ...

  6. BZOJ_3935_Rbtree

    https://lydsy.com/JudgeOnline/problem.php?id=3935 分析: 如果知道更改后的状态,那么代价和是否合法都能求出来 对不合法的情况也设一个估价函数. 随机这 ...

  7. 【C++ Primer 5th】Chapter 15

    摘要: 1. 面向对象程序设计的核心思想是数据抽象.继承和动态绑定.数据抽象将类的接口和实现分离:继承定义相似的类型并对齐相似关系建模:动态绑定,在一定程度上忽略相似类型的区别,而以统一的方式使用它们 ...

  8. docker的操作

    查询容器 docker ps  只能查询到正在运行的docker镜像: 如果添加上-a的选项,则会显示所有的(包括已经exit,未启动)的容器 基于一个镜像来构建(run)容器,并启动 docker ...

  9. 算法导论笔记——第二十章 van Emde Boas树

    当关键字是有界范围内的整数时,能够规避Ω(lglgn)下界的限制,那么在类似的场景下,我们应弄清楚o(lgn)时间内是否可以完成优先队列的每个操作.在本章中,我们将看到:van Emde Boas树支 ...

  10. Linux 运维面试问题总结

    Linux 运维面试问题总结 1.详细描述mysql主从复制的方式?(类似跟DRBD高可用的协议类型) 答: (1)同步复制(C协议:sync):只有在本地和远程磁盘都确定写入已完成时,主节点才会认为 ...