流编程
实战案例:集合与流操作对比
分别使用集合操作及Stream流操作,完成对实际应用场景中数据处理。直观感受流操作带来的便捷性
需求:
1.想看看购物车里有什么商品
2.图书类都给买
3.其余商品中买两件最贵的
4.只需要两件商品的名称和总价
传统操作
/**
* 以最原始的集合操作实现以上的需求
*/
@Test
public void oldCartHandle(){
/**
* 1.打印所有商品
*/
List<Sku> cartSkuList = CartService.getCartSkuList();
/**
* 1.打印所有商品
*/
for (Sku sku:cartSkuList) {
System.out.println(JSON.toJSONString(sku,true));
}
/**
* 2.图书类都给买
*/
List<Sku> notBooks = new ArrayList<Sku>();
for (Sku sku:cartSkuList) {
if (!(sku.getSkuCategory().equals(SkuCategoryEnum.BOOKS))){
notBooks.add(sku);
}
}
/**
* 3.其余商品中买两件最贵的
* 3.1 排序
*/
notBooks.sort(new Comparator<Sku>() {
@Override
public int compare(Sku sku1, Sku sku2) {
if (sku1.getTotalPrice() > sku2.getTotalPrice()){
return -1;
}else if(sku1.getTotalPrice() < sku2.getTotalPrice()){
return 1;
}else {
return 0;
}
}
});
/**
* 3.2 只需要前两个
*/
List<Sku> top2SkuList = new ArrayList<Sku>();
for (int i = 0; i < 2; i++) {
top2SkuList.add(notBooks.get(i));
}
/**
* 4.只需要两件商品的名称和总价
*/
Double money = 0.0;
for (Sku sku:top2SkuList) {
money += sku.getTotalPrice();
}
/**
* 获取两件商品名称
*/
List<String> resultSkuNameList = new ArrayList<String>();
for (Sku sku: top2SkuList) {
resultSkuNameList.add(sku.getSkuName());
}
/**
* 打印输出结果
*/
System.out.println(JSON.toJSONString(resultSkuNameList,true));
System.out.println("总价="+money);
}
Stream流操作
/**
* 以Stream流操作实现以上的需求
*/
@Test
public void newCartHandle(){
AtomicReference<Double> money = new AtomicReference<>(Double.valueOf(0.0));
List<String> resultSkuNameList = CartService.getCartSkuList()
.stream()
/**
* 1.打印商品信息
*/
.peek(sku -> System.out.println(JSON.toJSONString(sku, true)))
/**
* 2.过滤出图书类信息
*/
.filter(sku -> !SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
/**
* 3.对商品进行排序
*/
.sorted(Comparator.comparing(Sku::getTotalPrice).reversed())
/**
* 4.取出前两个商品
*/
.limit(2)
/**
* 金额累加
*/
.peek(sku -> money.set(money.get() + sku.getTotalPrice()))
/**
* 商品名称
*/
.map(sku -> sku.getSkuName())
/**
* 结果收集
*/
.collect(Collectors.toList());
/**
* 打印输出结果
*/
System.out.println(JSON.toJSONString(resultSkuNameList,true));
System.out.println("总价="+money.get());
}
流是什么?
- JDK1.8引入的新成员,以声明式方式处理集合数据
- 将基础操作链接起来,完成复杂的数据处理流水线
- 提供透明的并行处理
定义:从支持数据处理操作的源生成的元素序列
流与集合的区别
- 集合面向存储,流面向计算
- 集合可以遍历多次,流只能遍历一次
- 集合需要外部迭代,流内部迭代
流的组成
数据源、中间操作、终端操作
流操作的分类
实战案例:流的使用
常用的中间操作
/**
* 演示流的各种中间操作
*/
public class StreamOperator {
List<Sku> list ;
@Before
public void init(){
list = CartService.getCartSkuList();
}
/**
* filter 使用:过滤掉不符合断言判断的数据
*/
@Test
public void filterTest(){
list.stream()
//filter
.filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
.forEach(item -> System.out.println(JSON.toJSONString(item,true)));
}
/**
* map 使用:将一个元素转换为另一个元素
*/
@Test
public void mapTest(){
list.stream()
// map
.map(sku -> sku.getSkuName())
.forEach(item -> System.out.println(JSON.toJSONString(item,true)));
}
/**
* flatMap 使用:将一个对象转换成流
*/
@Test
public void flatMapTest(){
list.stream()
//flatMap 扁平化 map
.flatMap(sku -> Arrays.stream(sku
.getSkuName().split("")))
.forEach(item -> System.out.println(JSON.toJSONString(item,true)));
}
/**
* peek 使用:对流中元素进行遍历操作,与Foreach类似,但不会销毁流元素
* foreach 与 peek是交替执行的
*/
@Test
public void peekTest(){
list.stream()
//peek()
.peek(sku -> System.out.println(sku.getSkuName()))
.forEach(item -> System.out.println(JSON.toJSONString(item,true)));
}
/**
* sorted: 排序操作,可选择自然排序或指定排序规则,有状态操作
*/
@Test
public void sortTest(){
list.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
//sorted
.sorted(Comparator.comparing(Sku::getTotalPrice))
.forEach(item -> System.out.println(JSON.toJSONString(item,true)));
}
/**
* dinstant: 去重操作,有状态操作
*/
@Test
public void dinstantTest(){
list.stream()
.map(sku -> sku.getSkuCategory())
.distinct()
.forEach(item -> System.out.println(JSON.toJSONString(item,true)));
}
/**
* skip: 跳过前N条数据
*/
@Test
public void skipTest(){
list.stream()
.sorted(Comparator.comparing(Sku::getSkuPrice))
//过滤掉前两条数据
.skip(2)
.forEach(item->System.out.println(JSON.toJSONString(item,true)));
}
/**
* limit: 取前N条数据
*/
@Test
public void limitTest(){
list.stream()
.sorted(Comparator.comparing(Sku::getSkuPrice))
//模拟分页 第一页,一页显示两条
.skip( 1 * 2)
.limit(2)
.forEach(item->System.out.println(JSON.toJSONString(item,true)));
}
}
常用的终端操作
/**
* allMatch 使用: 终端操作,并且是一个短路操作,全部满足才会返回true
*/
@Test
public void allMatchTest(){
boolean match = list.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
//allMatch
.allMatch(sku -> sku.getTotalPrice() > 100);
System.out.println(match);
}
/**
* anyMatchsh 使用:有一个满足就返回TRUE
*/
@Test
public void anyMatchshTest(){
boolean anyMatch = list.stream()
.anyMatch(sku -> sku.getTotalPrice() > 1000);
System.out.println(anyMatch);
}
/**
* noneMatch使用:所有的都没匹配上返回true
*/
@Test
public void noneMatchshTest(){
boolean noneMatch = list.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.noneMatch(sku -> sku.getSkuPrice() > 10000);
System.out.println(noneMatch);
}
/**
* findFirst使用:找到第一个元素
*/
@Test
public void findFirstTest(){
Optional<Sku> first = list.stream()
.findFirst();
System.out.println(JSON.toJSONString(first.get(),true));
}
/**
* findAny 使用:在并行操作上效率高于 findFirst
*/
@Test
public void findAnyTest(){
Optional<Sku> any = list.stream()
.findAny();
System.out.println(JSON.toJSONString(any.get(),true));
}
/**
* max 使用:获取最大值
*/
@Test
public void maxTest(){
OptionalDouble max = list.stream()
//获取总价
//maptoDouble:将一个元素转化为double元素
.mapToDouble(Sku::getSkuPrice)
//获取总价最大值
.max();
System.out.println(JSON.toJSONString(max.getAsDouble(),true));
}
/**
* min 使用:获取最小值
*/
@Test
public void minTest(){
OptionalDouble min = list.stream()
//获取总价
//maptoDouble:将一个元素转化为double元素
.mapToDouble(Sku::getSkuPrice)
//获取总价最大值
.min();
System.out.println(JSON.toJSONString(min.getAsDouble(),true));
}
/**
* count 使用:获取元素总数
*/
@Test
public void countTest(){
long count = list.stream()
.count();
System.out.println(count);
}
流的构建
- 由值创建流
- 由数组创建流
- 由文件创建流
- 由函数生成流
实战案例:演示流的四种构建形式
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* 演示流的四种构建形式
*/
public class StreamConstructor {
//由值创建流
@Test
public void streamFromValue(){
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);
integerStream.forEach(System.out :: println);
}
//由数组创建流
@Test
public void streamFromArray(){
int [] number = {1,2,3,4,5,6};
IntStream stream = Arrays.stream(number);
stream.forEach(System.out :: println);
}
//由文件创建流
@Test
public void streamFromFile() throws IOException {
Stream<String> lines = Files.lines(Paths.get("D:\\demo\\app\\src\\main\\java\\com\\ausware\\yaowenyu\\steam\\StreamConstructor.java"));
lines.forEach(System.out :: println);
}
//由函数创建流
@Test
public void streamFromFunction(){
//方式一:
// Stream<Integer> iterate = Stream.iterate(0, n -> n + 2);
// iterate.forEach(System.out::println);
//方式二:
Stream<Double> generate = Stream.generate(Math::random);
generate.limit(100)
.forEach(System.out::println);
}
}
收集器简介
- 将流中的元素累积成一个结果
- 作用于终端操作 collect()上
- collect/Collector/Collectors
预定义收集器功能
- 将流元素归约和汇总为一个值
- 将流元素分组
- 将流元素分区
实战案例:预定义收集器
import com.alibaba.fastjson.JSON;
import com.ausware.yaowenyu.lambda.cart.CartService;
import com.ausware.yaowenyu.lambda.cart.Sku;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 常见预定义收集器使用
*/
public class StreamCollector {
/**
* toList 使用:将流转化为集合
*/
@Test
public void toListTest(){
List<Sku> list = CartService.getCartSkuList();
List<Sku> collect = list.stream()
.filter(sku -> sku.getTotalPrice() > 1000)
.collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect,true));
}
/**
* group 使用:将流按照指定条件进行分组
*/
@Test
public void gruopTest(){
List<Sku> list = CartService.getCartSkuList();
Map<Enum, List<Sku>> collect = list.stream()
.collect(Collectors.groupingBy(sku -> sku.getSkuCategory()));
System.out.println(JSON.toJSONString(collect,true));
}
/**
* partition 使用:将流按照指定条件进行分区
*/
@Test
public void partitionTest(){
List<Sku> list = CartService.getCartSkuList();
Map<Boolean, List<Sku>> collect = list.stream()
.collect(Collectors.partitioningBy(sku -> sku.getTotalPrice() > 1000));
System.out.println(JSON.toJSONString(collect,true));
}
}
归约与汇总
- 归约:将Stream 流中元素转换成一个值,只返回一个值
reduce接口参数
实战案例:自定义归约
根据一批订单信息,计算平均商品价格
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Test;
import java.util.ArrayList;
/**
* 归约与汇总操作
*/
public class ReduceAndCollectTest {
@Test
public void reduceTest(){
@Data
@AllArgsConstructor
@NoArgsConstructor
class Order{
//订单编号
private Integer id;
//订单总金额
private Double totalAmount;
//商品数量
private Integer productCount;
}
//准备数据
ArrayList<Order> orders = new ArrayList<>();
{
orders.add(new Order(1,25.12,2));
orders.add(new Order(2,12.26,4));
orders.add(new Order(3,23.56,12));
/**
* 传统方式:
* 1.计算商品数量
* 2.计算商品总金额
*/
/**
* 汇总商品数量合总金额
*/
Order reduce = orders.stream()
.parallel()
.reduce(
//初始化值
new Order(0, 0.0, 0),
//Stream中两个元素的计算逻辑
(Order order1, Order order2) -> {
System.out.println("执行 计算逻辑 方法!!!");
int productCount = order1.getProductCount() + order2.getProductCount();
double totalAmount = order1.getTotalAmount() + order2.getTotalAmount();
return new Order(0, totalAmount, productCount);
},
//并行情况下多个并行结果如何合并
(Order order1, Order order2) -> {
System.out.println("执行 合并 方法!!!");
int productCount = order1.getProductCount() + order2.getProductCount();
double totalAmount = order1.getTotalAmount() + order2.getTotalAmount();
return new Order(0, totalAmount, productCount);
});
System.out.println(JSON.toJSONString(reduce,true));
}
}
}
- 汇总:将Stream流中元素转换成一个容器
实战案例:自定义收集
根据一批订单信息,计算每个用户的平均商品价格
@Test
public void collectTest(){
@Data
@AllArgsConstructor
@NoArgsConstructor
class Order{
//订单编号
private Integer id;
//订单总金额
private Double totalAmount;
//商品数量
private Integer productCount;
//账户
private String account;
}
ArrayList<Order> orders = new ArrayList<>();
{
orders.add(new Order(1,25.12,2,"张三"));
orders.add(new Order(2,12.26,4,"张三"));
orders.add(new Order(3,23.56,12,"李四"));
}
/**
* Map<用户账户,订单(商品数量和价格)>
*/
HashMap<String, Order> collect = orders.stream()
.parallel()
.collect(
() -> {
System.out.println("执行 初始化容器");
return new HashMap<String, Order>();
}
, (HashMap<String, Order> map, Order newOrder) -> {
System.out.println("执行 新元素添加到容器 操作");
/**
* 新元素的账号已经在map中
*/
String account = newOrder.getAccount();
//如果此账户已存在
if (map.containsKey(account)) {
Order order = map.get(account);
order.setProductCount(newOrder.getProductCount() + order.getProductCount());
order.setTotalAmount(newOrder.getTotalAmount() + order.getTotalAmount());
} else {
//如果不存在
map.put(account, newOrder);
}
},
(HashMap<String, Order> map1, HashMap<String, Order> map2) -> {
System.out.println("执行 并行结果合并 操作");
map2.forEach((key, value) -> {
map1.merge(key, value, (order1, order2) -> {
//注意:一定要用map1做合并,因为最后collect返回的是map1
return new Order(0,
order1.getTotalAmount() + order2.getTotalAmount(),
order1.getProductCount() + order2.getProductCount(),
key);
});
});
});
System.out.println(JSON.toJSONString(collect,true));
}
collect接口参数
收集器接口
实战案例一:查找
班级中有20名学生,每名学生有5门课的考试成绩。其中缺考的科目分数字段为空。需要找出有缺考的学生都叫什么名字。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 案例一
*/
public class CaseOne {
/**
* 考试成绩模型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class ExamStudentScore{
private String name;
private Integer score;
private String subject;
}
Map<String , List<ExamStudentScore>> studentMap = new HashMap<>();
@Before
public void init(){
ArrayList<ExamStudentScore> examStudentScores1 = new ArrayList<>();
examStudentScores1.add(new ExamStudentScore("张三",85,"语文"));
examStudentScores1.add(new ExamStudentScore("张三",90,"数学"));
examStudentScores1.add(new ExamStudentScore("张三",79,"英语"));
studentMap.put("张三",examStudentScores1);
ArrayList<ExamStudentScore> examStudentScores2 = new ArrayList<>();
examStudentScores2.add(new ExamStudentScore("李四",80,"语文"));
examStudentScores2.add(new ExamStudentScore("李四",null,"数学"));
examStudentScores2.add(new ExamStudentScore("李四",null,"英语"));
studentMap.put("李四",examStudentScores2);
ArrayList<ExamStudentScore> examStudentScores3 = new ArrayList<>();
examStudentScores3.add(new ExamStudentScore("王五",79,"语文"));
examStudentScores3.add(new ExamStudentScore("王五",null,"数学"));
examStudentScores3.add(new ExamStudentScore("王五",80,"英语"));
studentMap.put("王五",examStudentScores3);
}
@Test
public void findStudent(){
studentMap.forEach((studentName, scoreList) -> {
boolean anyMatch = scoreList.stream()
.anyMatch(score -> {
//anyMatch 找到任意一条符合条件的数据后就停止
//System.out.println(score);
return score.getScore() == null;
});
if (anyMatch){
System.out.println("此学生姓【"+studentName+"】有缺考情况!");
}
});
}
}
实战案例二:去重
标签管理功能模块。允许用户批量添加标签,后台需要对标签去重,并且需要防止数据库中存在同名的标签。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* 案例二:
*/
public class CaseTwo {
/**
* 用户请求的创建标签模型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class TagReqDTO{
//标签名称
private String name;
//标签值
private Integer age;
}
//从DB中查询出来的已经存在的标签名
List<String> tagListFromDB = new ArrayList<String>();
//用户请求的标签列表
List<TagReqDTO> tagListFromReq = new ArrayList<TagReqDTO>();
@Before
public void init(){
//数据库中存在的标签名列表
tagListFromDB.add("李四");
tagListFromDB.add("王五");
tagListFromDB.add("赵六");
//用户提交
tagListFromReq.add(new TagReqDTO("张三",10));
tagListFromReq.add(new TagReqDTO("李四",30));
tagListFromReq.add(new TagReqDTO("张三",10));
}
@Test
public void distinctTest(){
tagListFromReq.stream()
//true:通过测试,数据不过滤,false:未通过测试,数据被过滤
.filter(tag -> tagListFromDB.contains(tag.getName()))
//使用equals对元素进行比较
.distinct()
.forEach(tag->System.out.println(tag));
}
}
实战案例三:扁平化
权限管理功能模块。查询某用户所有角色下所包含的权限名称。
package com.ausware.yaowenyu.steam.test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.assertj.core.util.Lists;
import org.junit.Before;
import org.junit.Test;
import org.springframework.expression.spel.SpelEvaluationException;
import javax.management.relation.Role;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 案例三:
*/
public class CaseThree {
/**
* 角色数据模型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Role{
//权限列表
private List<Permission> permissions;
}
/**
* 权限
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Permission{
/**
* 权限名称
*/
private String name;
}
/**
* 用户角色列表
*/
List<Role> roleList;
@Before
public void init(){
roleList = new ArrayList<>();
Role adminRole = new Role();
List<Permission> adminPermissions = Lists.newArrayList(
new Permission("删除"),
new Permission("查看"),
new Permission("导出")
);
adminRole.setPermissions(adminPermissions);
Role userRole = new Role();
ArrayList<Permission> userPermissions = Lists.newArrayList(
new Permission("新建"),
new Permission("修改"),
new Permission("删除"),
new Permission("查看")
);
userRole.setPermissions(userPermissions);
roleList.add(adminRole);
roleList.add(userRole);
}
@Test
public void findPermission(){
roleList.stream()
//扁平化map
.flatMap(role -> role.getPermissions().stream())
// .peek(permission -> System.out.println("新的流元素:"+permission))
.distinct()
.forEach(permission -> System.out.println(permission));
// .collect(Collectors.toList());
}
}
实战案例四:分组
设计一个对外提供服务的接口,支持调用方传入多个账户编号查询订单。
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.assertj.core.util.Lists;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 案例四:
*/
public class CaseFour {
@Data
@AllArgsConstructor
@NoArgsConstructor
class Order{
private Integer orderId;
private String accountId;
}
//模拟数据库查询
public List<Order> selectFromDB(List<String> accountIds){
List<Order> orderList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
orderList.add(new Order(i,accountIds.get(i % accountIds.size())));
}
return orderList;
}
/**
* 接口
* @param accountIds
* @return
*/
public Map<String ,List<Order>> queryOrderByAcoiuntIds(List<String> accountIds){
return Optional.ofNullable(selectFromDB(accountIds))
.map(List::stream)
.orElseGet(Stream::empty)
.collect(Collectors.groupingBy((order)->order.getAccountId()));
}
@Test
public void test(){
Map<String, List<Order>> stringListMap = queryOrderByAcoiuntIds(Lists.newArrayList("张三", "李四", "王五"));
System.out.println(JSON.toJSONString(stringListMap,true));
}
}
实战案例五:排序
在股票中,撮合交易的原则是一段时间内的交易申请,价格越高的先成交﹔价格一样,下单时间最早的先成交﹔价格和时间一致,交易量大的先成交﹔如果价格、时间和交易量都一致,机构优先成交,散户最后成交。 现有一批交易申请数据,需要确认交易先后顺序。
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.Before;
import org.junit.Test;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* 案例五:
*/
public class CaseFive {
/**
* 交易实体模型
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Trade{
//下单价格
private BigDecimal price;
//下单时间
private LocalDateTime time;
//下单量
private Integer count;
//下单类型:机构/个人
private String type;
}
/**
* 一段时间内的交易申请
*/
List<Trade> trades;
@Before
public void init(){
trades = new ArrayList<>();
{
trades.add(new Trade(new BigDecimal(100),LocalDateTime.now().plusSeconds(1),500,"机构"));
trades.add(new Trade(new BigDecimal(101),LocalDateTime.now().plusSeconds(2),1,"个人"));
trades.add(new Trade(new BigDecimal(101),LocalDateTime.now().plusSeconds(1),1,"个人"));
trades.add(new Trade(new BigDecimal(100),LocalDateTime.now().plusSeconds(1),500,"个人"));
trades.add(new Trade(new BigDecimal(101),LocalDateTime.now().plusSeconds(1),1,"个人"));
trades.add(new Trade(new BigDecimal(100),LocalDateTime.now().plusSeconds(0),500,"个人"));
trades.add(new Trade(new BigDecimal(100),LocalDateTime.now().plusSeconds(0),2,"机构"));
}
}
@Test
public void sortTrade(){
System.out.println(JSON.toJSONString(trades,true));
List<Trade> collect = trades.stream()
.sorted(Comparator
//首先按照价格排序
.comparing(
Trade::getPrice,
//进行排序调整,将自然排序进行反转
Comparator.reverseOrder())
//按照时间先后进行排序
.thenComparing(Trade::getTime)
//按照交易量进行排序
.thenComparing(
Trade::getCount,
Comparator.reverseOrder())
//自定义排序规则
.thenComparing(
//要排序的字段值
Trade::getType,
//排序规则
(type1, type2) -> {
if ("机构".equals(type1) && "个人".equals(type2)) {
return -1;
} else if ("个人".equals(type1) && "机构".equals(type2)) {
return 1;
} else {
return 0;
}
})
)
.collect(Collectors.toList());
System.out.println("排序后结果:");
System.out.println(JSON.toJSONString(collect,true));
}
}