建议参考文章《用了多年Java,你的团队会使用Java8 Stream API吗?》
Stream是Java8中处理数组、集合的抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。
一个Stream表面上与一个集合很类似,集合中保存的是数据,而流中对数据的操作,类似于流水线的操作。
Stream的特点:
- Stream 自己不会存储元素
- 不会改变源对象。相反,他们会返回一个持有结果的新Stream
- 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
- 遵循“做什么,而不是怎么去做”的原则。只需要描述需要做什么,而不用考虑程序是怎样实现的
使用Stream,会有三个阶段(步骤):
- 创建一个Stream
- 在一个或多个步骤中,将初始Stream转化到另一个Stream的中间操作
- 使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该Stream就不会在被使用了
Stream的创建方法
//Stream.of方法
Stream<String> stream = Stream.of("hello", "xxx", "yyyy", "beijing", "shanghai");
//Arrays.stream方法
IntStream stream1 = Arrays.stream(new int[]{100, 200, 300, 400, 500});
//集合方法【常用】
List<String> list = new ArrayList<>();
Stream<String> stream2 = list.stream();
//并行流:多核,充分发挥CPU,提高效率
Stream<String> stringStream = list.parallelStream();
Stream中间操作
中间操作包括:map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
多个中间操作可以连接起来形成一个流水线,除非流水 线上触发终止操作,否则中间操作不会执行任何的处理! 而在终止操作时一次性全部处理,称为“惰性求值”。
一种一点到底的操作。
代码演示各种方法的操作,这里首先创建了集合,并引入了诸多元素(包含重复de)。
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Jack", 20, "男", 65000));
employees.add(new Employee("Peter", 27,"nv", 80000));
employees.add(new Employee("鬼头", 23,"男", 60000));
employees.add(new Employee("鬼头", 23,"男", 60000));
employees.add(new Employee("Boss", 30,"女", 10000));
employees.add(new Employee("Boss", 30,"女", 10000));
employees.add(new Employee("鸣人", 16, "男",27000));
employees.add(new Employee("鸣人", 16,"男", 27000));
filter 筛选
//筛选年龄大于25的,并遍历
employees.stream()
.filter(e -> e.getAge() > 25)
.forEach(System.out::println);
limit——截断流,使其元素不超过给定数量
//只展示两个元素并遍历
employees.stream()
.limit(2)
.forEach(System.out::println);
skip(n) —— 跳过元素
//跳过两个元素,然后只展示后面元素中的两个
employees.stream()
.skip(2)
.limit(2)
.forEach(System.out::println);
distinct去重
//筛选,通过流所生成元素的 equals() 去除重复元素,所以必须要重写equals方法
employees.stream()
.distinct()
.forEach(System.out::println);
map映射
//map映射方法获取姓名并遍历
employees.stream()
.distinct()
.map(e -> e.getName())
.forEach(System.out::println);
sorted排序
//自然排序
mployees.stream()
.distinct()
.map(Employee::getAge)
.sorted()
.forEach(System.out::println);
//定制排序
employees.stream()
.distinct()
.sorted((o1, o2) -> {
//这里注意,double类型数据一定使用【Double.compare(o1, o2)方法进行比较】
return (o1.getAge() - o2.getAge()) == 0 ? Double.compare(o2.getSalary(), o1.getSalary()) : (o1.getAge() - o2.getAge());
})
.forEach(System.out::println);
//【建议使用】:Comparator.comparing,Comparator.reverseOrder()是倒序
.sorted(Comparator.comparing(e -> e.getAge, Comparator.reverseOrder()))
Stream终止操作
终止操作包括:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。、
allMatch查是否匹配所有元素
boolean allMatch = employees.stream()
.allMatch(e -> e.getGender().equals("男"));
anyMatch检查是否至少匹配一个元素
boolean anyMatch = employees.stream()
.anyMatch(e -> e.getGender().equals("女"));
noneMatch检查是否没有匹配的元素
boolean noneMatch = employees.stream()
.noneMatch(e -> e.getGender().equals("妖"));
findFirst返回第一个元素 / findAny——返回当前流中的任意元素
Employee employee = employees.stream()
.findFirst()
.get();
System.out.println(employee);
count返回流中元素的总个数
long count = employees.stream()
.count();
max返回流中最大值 / min返回流中最小值
Employee max = employees.stream()
.max((o1, o2) -> {
return Double.compare(o1.getSalary(), o2.getSalary());
}).get();
System.out.println(max);
Employee employee1 = employees.stream()
.min((o1, o2) -> {
return o1.getAge() - o2.getAge();
}).get();
System.out.println(employee1);
//【与排序一样,建议使用Comparator.comparing中的方法】
reduce归约
//reduce归约_可以将流中元素反复结合起来,得到一个值
reduce(T identity, BinaryOperator);
reduce(BinaryOperator);
//求和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream().reduce(0, (x, y) -> x + y);
//计算员工的工资总和
Double reduce = employees.stream()
.map(e -> e.getSalary())
.reduce(0.0, (x, y) -> x + y);
collect 将流转换为其他形式
接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。
//获取所有员工的姓名集合
List<String> list = employees.stream()
.map(e -> e.getName())
.collect(Collectors.toList());
Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行,并行parallelStream()用法与串行Stream一样。
分组
public void CollectorsTest(){
Student stu1 = new Student("stu1",11);
Student stu2 = new Student("stu2",12);
Student stu3 = new Student("stu3",13);
Student stu4 = new Student("stu4",14);
Student stu5 = new Student("stu5",15);
List<Student> list = Lists.newArrayList(stu1,stu2,stu3,stu4,stu5);
//将所有学生的姓名收集到新的list中
List<String> nameList = list.stream().map(Student::getName).collect(Collectors.toList());
//同样可以收集到Set中
Set<String> nameSet = list.stream().map(Student::getName).collect(Collectors.toSet());
//将每个学生的name,age转换成Map,若有名称相同的会造成key相同导致失败
Map<String,Integer> map = list.stream().collect(Collectors.toMap(Student::getName,Student::getAge));
//字符串连接
String nameJoin = list.stream().map(Student::getName).collect(Collectors.joining(";","(",")"));
//---聚合操作----
//count
Long count = list.stream().collect(Collectors.counting());
// max
Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get();
// sum
Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge));
// avg
Double avgAge = list.stream().collect(Collectors.averagingDouble(Student::getAge));
// 所有聚合操作的返回
DoubleSummaryStatistics summaryStatistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
summaryStatistics.getSum();
summaryStatistics.getAverage();
summaryStatistics.getCount();
summaryStatistics.getMax();
summaryStatistics.getMin();
//group 按年龄分组
Map<Integer,List<Student>> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getAge));
//分区,分成两部分,一部分大于12岁,一部分小于等于12岁
Map<Boolean,List<Student>> agePartition = list.stream().collect(Collectors.partitioningBy(x -> x.getAge() > 12));
//规约
Integer reducingAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::max)).get();
}
public void countTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
Long count = list.stream().count();
}
public void maxAndminTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
Integer max = list.stream().max(Integer::compareTo).get();
Integer min = list.stream().min(Integer::compareTo).get();
}
public void reduceTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
Integer sum = list.stream().reduce(0,(x,y) -> x + y);
}
实操案例1
场景:有一个产品的集合,我们需要根据其产品名称进行数量分类
//(1)使用collect.toMap处理,对key冲突处理
Map<String, Integer> statistic = requestStatisticDOS.stream().collect(Collectors.toMap(
Product::getName,
v -> 1,
(o, n) -> ++o));
默认必须赋值1,数据存在即为1个,另外,第三个参数是key 冲突处理,里面的o和n表示oldKey和newKey,这里必须是oldKey进行先++操作,这样返回的才是加之后的数据。
//(2)使用groupingBy
Map<String, Long> collect = requestStatisticDOS.stream().collect(Collectors.groupingBy(Product::getName, Collectors.counting()));
实操案例2
/**
* 场景:
* 数据库查询条件证件号、姓名、会员等级
* 返回会员集合List<Map<String, String>>类型
* 1、过滤关键字段,证件号:identifyId、姓名、等级都不能为空
* 2、根据证件号,去重
* 3、根据等级由大到小倒序排列
* 4、截取,只返回三个数据即可
*/
//模拟数据,我们得到如下的数据 members
{"level":"9","name":"张三9","identifyId":"1306841999"}
{"level":"4","name":"张三4","identifyId":"1306841994"}
{"level":"1","name":"张三1","identifyId":"1306841991"}
{"level":"3","name":"张三3","identifyId":"1306841993"}
{"level":"6","name":"张三6","identifyId":"1306841996"}
{"level":"7","name":"张三7","identifyId":"1306841997"}
{"level":"5","name":"张三5","identifyId":"1306841995"}
{"level":"3","name":"张三3","identifyId":"1306841993"}
{"level":"6","name":"张三6","identifyId":"1306841996"}
{"level":"8","name":"张三8","identifyId":"1306841998"}
{"level":"9","name":"张三9","identifyId":"1306841999"}
{"level":"2","name":"张三2","identifyId":"1306841992"}
{"level":"10","name":"张三10","identifyId":"13068419910"}
//对数据进行流式处理
members = members.stream()
//1、过滤
.filter(e -> StringUtils.isNotEmpty(e.get("identifyId"))
&& StringUtils.isNotEmpty(e.get("name"))
&& StringUtils.isNotEmpty(e.get("level")))
//2、根据证件号排重:分组map<"identifyId", map<String, String>>
.collect(Collectors.toMap(key ->
key.get("identifyId"), //选取证件号为key
value -> value, //value就是成员Map本身
(o, n) -> o)) //如果key冲突,Old和New,选择Old,排重操作
.values() //获取所有的map
//3、排序,Comparator.reverseOrder()降序
.stream().sorted(Comparator.comparing(e -> e.get("level"), Comparator.reverseOrder()))
//截取
.limit(5)
//转为list
.collect(Collectors.toList());
//打印结果
{"level":"9","name":"张三9","identifyId":"1306841999"}
{"level":"8","name":"张三8","identifyId":"1306841998"}
{"level":"7","name":"张三7","identifyId":"1306841997"}
{"level":"6","name":"张三6","identifyId":"1306841996"}
{"level":"5","name":"张三5","identifyId":"1306841995"}
实操案例3——faltMap
flatMap可以在循环里新建集合,最后collect的时候进行组合返回结果
/**
* 商品类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods {
/**
* 商品id
*/
private Long goodId;
/**
* 商品名称
*/
private String goodName;
/**
* 商品价格
*/
private String goodPrice;
}
/**
* 品牌类:包含一系列商品
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Brand {
/**
* 品牌id
*/
private Long id;
/**
* 品牌名称
*/
private String brandName;
/**
* 包含的商品
*/
private List<Goods> goodsList;
}
/**
* 商圈类:包含一系列品牌
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BusinessCircle {
/**
* 品牌集合
*/
private List<Brand> brandList;
}
/**
* 验货任务表,商品维度
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CheckGoodsTask {
/**
* 商品的id
*/
private Long goodsId;
/**
* 商品名称
*/
private String goodsName;
/**
* 对应品牌的id
*/
private Long brandId;
}
场景:①形成Map;②形成计划任务
@RunWith(JUnit4.class)
public class JdkStreamTest {
/**
* 测试 flatMap
*/
@Test
public void testFlatMap() {
List<BusinessCircle> circles = getBrandList();
/*
* 我们的目的是将List<BusinessCircle>转为Map
* Map的 key = 品牌的id
* Map的 value = Set<String>商品的名称的set集合
* 很显然,我们需要进行商圈->品牌->商品的三重过滤,然后组合成结果
* 我们可以考虑使用flatMap展开处理,返回内层list的steam进行进一步操作
*/
Map<String, Set<String>> result = circles.stream()
/* 我们是不需要商圈信息的,所以这里我们可以直接展开内层的品牌list
* 源码:<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
* 显然需要返回一个stream,以方便后续操作
*/
.flatMap(v -> v.getBrandList().stream())
.collect(Collectors.toMap(
k -> String.valueOf(k.getId()),
v -> v.getGoodsList().stream()
.map(Goods::getGoodName)
.collect(Collectors.toSet()),
(o ,n) -> o));
System.out.println(JackSonUtil.objToJson(result));
/*
* 新的要求:遍历获取商品,并以商品维度填充自动盘货计划任务表对象
* CheckGoodsTask:里面包含有商品的id和名称,以及其对应的品牌id
* 同样涉及到内层嵌套循环,还是使用flatMap
*/
List<CheckGoodsTask> collect = circles.stream()
.flatMap(c -> c.getBrandList().stream())
.flatMap(b -> {
// 这里可以内部新建List,最终collect规约的时候将全部list组合
List<CheckGoodsTask> tasks = Lists.newArrayList();
b.getGoodsList()
.forEach(g ->
tasks.add(new CheckGoodsTask(
g.getGoodId(),
g.getGoodName(),
b.getId()))
);
//注意;flatMap必须给Stream类型返回值,方便后续操作
return Stream.of(tasks);
})
//以上数据返回结果是Stream<List<CheckGoodsTask>>
//如果直接Collectors.toList那么将按商圈维度返回List<List<CheckGoodsTask>>
//需要再次展开,集合成一个list
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(JackSonUtil.objToJson(collect));
}
/**
* 获取一系列的商圈,每个商圈有一系列品牌,每个品牌有一系列的商品,这样构成一个三维的list集合
* @return 商圈list集合
*/
private List<BusinessCircle> getBrandList() {
List<BusinessCircle> circles = new ArrayList<>(2);
for (int k = 0; k < 2; k++) {
List<Brand> brands = new ArrayList<>(5);
for (int i = 0; i < 5; i++) {
List<Goods> goodsList = new ArrayList<>(3);
for (int j = 0; j < 3; j++) {
goodsList.add(new Goods((long) j,
"商品" + j,
String.valueOf(j * 20)));
}
brands.add(new Brand((long)i,
"品牌" + k,
goodsList));
}
circles.add(new BusinessCircle(brands));
}
return circles;
}
}
实操案例4——对集合中元素的属性赋值
/**
* 对list中元素进行赋值
*/
@Test
public void testSetInList() {
List<Goods> goodsList = new ArrayList<>(3);
goodsList.add(new Goods(1L, "苹果", "22"));
goodsList.add(new Goods(2L, "鸭梨", "55"));
goodsList.add(new Goods(3L, "香蕉", "99"));
//使用peek,可以对值进行修改
goodsList = goodsList.stream()
.peek(v -> v.setGoodName(v.getGoodName() + "Cn"))
.collect(Collectors.toList());
//也可以使用map进行处理,但是注意必须返回元素,因为collect规约的时候会对元素进行合并
goodsList = goodsList.stream()
.map(v -> {
v.setGoodPrice(v.getGoodPrice()+"$");
return v;
})
.collect(Collectors.toList());
//可以使用map进行复杂的分组
//比如将list转为Map<GoodName, Set<GoodPrice>>
Map<String, Set<String>> collect = goodsList.stream()
.collect(Collectors.toMap(
Goods::getGoodName,
v -> {
Set<String> set = Sets.newHashSet();
set.add(v.getGoodPrice());
//返回集合,后面key冲突的时候排重合并
return set;
},
(o, n) -> {
//如果key冲突,使用addAll将集合并入
o.addAll(n);
return o;
}
));
//还可以使用 Collectors.groupingBy进行处理
Map<String, Set<String>> result = goodsList.stream()
.collect(
Collectors.groupingBy(
Goods::getGoodName,
Collectors.mapping(Goods::getGoodPrice, Collectors.toSet())));
//使用map处理
System.out.println(JackSonUtil.objToJson(result));
}
//打印结果
{"苹果Cn":["22$","55$"],"香蕉Cn":["99$"]}