说起流,咱们会想起手机,电脑组装流水线,物流仓库商品包装流水线等等。如果把手机 , 电脑, 包裹看做最终后果的话, 那么加工商品前的各种零部件就可以看做数据源,而两头一系列的加工作业操作,就可以看做流的解决。
流的概念
Java Se 中对于流的操作有输入输出 IO 流, 而 Java8 中引入的 Stream 属于 Java API 中的一个新成员,它容许你以申明性形式解决数据汇合,Stream 应用一种相似 SQL 语句从数据库查问数据的直观形式来提供一种对 Java 汇合运算和表白的高阶形象。留神这里的流操作能够看做是对汇合数据的解决。
简略来说, 流是一种数据渠道, 用于操作数据源 (汇合、数组、文件等) 所生产的元素序列。
- 源 - 流会应用一个提供数据的源, 如汇合、数组或输出 | 输入资源。
从有序集生成流时会保留原有的程序。由列表生成的流,其元素程序与列表统一
- 元素序列 - 就像汇合一样,流也提供了一个接口, 能够拜访特定元素类型的一组有序值。
- 数据处理操作 - 流的数据处理性能反对相似于数据库的操作(数据筛选、过滤、排序等操作)。
- 流水线 - 多个流操作自身会返回一个流,多个操作就能够链接起来, 成为数据处理的一道流水线。
流 & 汇合
- 计算的期间
汇合中数据都是计算结束的数据,例如从数据库中查问用户记录 按用户 id 查问 降序排列 而后通过 list 接管用户记录,数据的计算已在放入汇合前实现。
流中数据按需计算,依照使用者的须要计算数据,例如通过搜索引擎进行搜寻,搜寻进去的条目并不是全副出现进去的,而且先显示最合乎的前 10 条或者前 20 条,只有在点击“下一页”的时候,才会再输入新的 10 条。流的计算也是这样,当用户须要对应数据时,Stream 才会对其进行计算解决。
- 内部迭代与外部迭代
把汇合比作一个工厂的仓库的话,一开始工厂硬件比较落后,要对货物作什么批改,此时工人亲自走进仓库对货物进行解决,有时候还要将解决后的货物转运到另一个仓库中。此时对于开发者来说须要亲自去做迭代,一个个地找到须要的货物,并进行解决,这叫做内部迭代。
当工厂倒退起来后,装备了流水线作业,工厂只有依据需要设计出相应的流水线,而后工人只有把货物放到流水线上,就能够等着接管成绩了,而且流水线还能够依据要求间接把货物输送到相应的仓库。
这就叫做外部迭代,流水线曾经帮你把迭代给实现了,你只须要说要干什么就能够了(即设计出正当的流水线)。相当于 Java8 引入的 Stream 对数据的解决实现了”自动化”操作。
流操作过程
整个流操作就是一条流水线,将元素放在流水线上一个个地进行解决。须要留神的是: 很多流操作自身就会返回一个流,所以多个操作能够间接连接起来,如下图这样,操作能够进行链式调用,并且并行流还能够实现数据流并行处理操作。
总的来说,流操作过程分为三个阶段:
- 创立
借助数据源创立流对象
- 两头解决
筛选、切片、映射、排序等两头操作
- 终止流
匹配、汇总、分组等终止操作
流的创立
对流操作首先要创立对应的流,流的创立集中模式如下:
1 汇合创立流
在 Java 8 中, 汇合接口有两个办法来生成流:
- stream() − 为汇合创立串行流。
- parallelStream() − 为汇合创立并行流。
示例代码如下:
public static void main(String[] args) {
/**
* 定义汇合 l1 并为汇合创立串行流
*/
List<String> l1 = Arrays.asList("周星驰", "周杰伦", "周星星", "周润发");
// 返回串行流
l1.stream();
// 返回并行流
l1.parallelStream();}
上述操作失去的流是通过原始数据转换过去的流,除了这种流创立的基本操作外,对于流的创立还有以下几种形式。
2 值创立流
Stream.of(T…):Stream.of(“aa”, “bb”) 生成流
// 值创立流 生成一个字符串流
Stream<String> stream = Stream.of("java8", "Spring", "SpringCloud");
stream.forEach(System.out::println);
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 文件生成流
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()));
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());
归约 & 收集
1 归约
将流中元素重复联合起来,失去一个值的操作
- 计算无效订单总金额
// 计算无效订单总金额
System.out.println("无效订单总金额:" +
ordersList.stream()
.filter((order) -> order.getIsValid() == 1)
.map(Order::getTotal)
.reduce(Double::sum)
.get());
2 Collector 数据收集
将流转换为其余模式,coollect 办法作为终端操作,接管一个 Collector 接口的实现,用于给 Stream 中元素做汇总的办法。最罕用的办法,把流中所有元素收集到一个 List, Set 或 Collection 中。
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))
);
分组 & 分区
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("-----------------------");
});
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 流就介绍到这里了,应该是十分详尽了,心愿大家喜爱。