利用java8新特性,可以用简洁高效的代码来实现一些数据处理。
定义1个Apple对象:
public class Apple {
private Integer id;
private String name;
private BigDecimal money;
private Integer num;
public Apple(Integer id, String name, BigDecimal money, Integer num) {
this.id = id;
this.name = name;
this.money = money;
this.num = num;
}
}
添加一些测试数据:
List<Apple> appleList = new ArrayList<>();//存放apple对象集合
Apple apple1 = new Apple(1,"苹果1",new BigDecimal("3.25"),10);
Apple apple12 = new Apple(1,"苹果2",new BigDecimal("1.35"),20);
Apple apple2 = new Apple(2,"香蕉",new BigDecimal("2.89"),30);
Apple apple3 = new Apple(3,"荔枝",new BigDecimal("9.99"),40);
appleList.add(apple1);
appleList.add(apple12);
appleList.add(apple2);
appleList.add(apple3);
1、分组
List里面的对象元素,以某个属性来分组,例如,以id分组,将id相同的放在一起:
//List 以ID分组 Map<Integer,List<Apple>>
Map<Integer, List<Apple>> groupBy = appleList.stream().collect(Collectors.groupingBy(Apple::getId));
System.err.println("groupBy:"+groupBy);
{1=[Apple{id=1, name='苹果1', money=3.25, num=10}, Apple{id=1, name='苹果2', money=1.35, num=20}], 2=[Apple{id=2, name='香蕉', money=2.89, num=30}], 3=[Apple{id=3, name='荔枝', money=9.99, num=40}]}
2、List转Map
id为key,apple对象为value,可以这么做:
/**
* List -> Map
* 需要注意的是:
* toMap 如果集合对象有重复的key,会报错Duplicate key ....
* apple1,apple12的id都为1。
* 可以用 (k1,k2)->k1 来设置,如果有重复的key,则保留key1,舍弃key2
*/
Map<Integer, Apple> appleMap = appleList.stream().collect(Collectors.toMap(Apple::getId, a -> a,(k1,k2)->k1));
打印appleMap
{1=Apple{id=1, name='苹果1', money=3.25, num=10}, 2=Apple{id=2, name='香蕉', money=2.89, num=30}, 3=Apple{id=3, name='荔枝', money=9.99, num=40}}
3、过滤Filter
从集合中过滤出来符合条件的元素:
//过滤出符合条件的数据
List<Apple> filterList = appleList.stream().filter(a -> a.getName().equals("香蕉")).collect(Collectors.toList());
System.err.println("filterList:"+filterList);
[Apple{id=2, name=‘香蕉’, money=2.89, num=30}]
4.求和
将集合中的数据按照某个属性求和:
//计算 总金额
BigDecimal totalMoney = appleList.stream().map(Apple::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
System.err.println("totalMoney:"+totalMoney); //totalMoney:17.48
5.查找流中最大 最小值
Collectors.maxBy 和 Collectors.minBy 来计算流中的最大或最小值。
Optional<Dish> maxDish = Dish.menu.stream().
collect(Collectors.maxBy(Comparator.comparing(Dish::getCalories)));
maxDish.ifPresent(System.out::println);
Optional<Dish> minDish = Dish.menu.stream().
collect(Collectors.minBy(Comparator.comparing(Dish::getCalories)));
minDish.ifPresent(System.out::println);
6.去重
import static java.util.Comparator.comparingLong;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;
// 根据id去重
List<Person> unique = appleList.stream().collect(
collectingAndThen(
toCollection(() -> new TreeSet<>(comparingLong(Apple::getId))), ArrayList::new)
);
下表展示 Collectors 类的静态工厂方法。
原文:https://blog.csdn.net/lu930124/article/details/77595585
stream API 常用
2.创建Stream
常用的创建Stream的方式有三种:
通过集合类的相关方法
通过Stream接口的静态工厂方法
其他能产生流的类的方法
2.1 集合类的相关方法
Collection接口中定义了一个stream方法
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
因而所有Collection的子类都能通过此方法开启Stream,常见的就是从数据库查询出一个List,然后使用Stream进行处理。
List<User> userList = // 数据库查询
long teenagerNum = userList.stream().filter(u -> u.getAge() < 20).count();
从数据库查询出一组User,获得出年龄小于20岁的用户数。
还有一种情况是,参数是一个数组,可以使用Arrays工具类来处理
int[] nums = {1, 2, 3, 2};
Arrays.stream(nums).distinct().forEach(System.out::println);
distinct方法用来去重,即将一组整数去重后打印出来
2.2 Stream静态工厂方法
Stream接口中也提供多种静态方法来辅助我们构造Stream
1.of方法,非常好用的方法,接受可变参数
Stream<String> stringStream = Stream.of("1", "2", "3");
或者直接传入数组
String[] strs = {"1", "2", "3"};
Stream<String> stringStream = Stream.of(strs);
2.generator方法,生成一个无限长度的Stream,需要一个自定义的Supplier类,比如创建一个随机数的Stream
Stream.generate(new Supplier<Long>() {
@Override
public Long get() {
return Math.random();
}
});
可以使用lamada表达式及Method Reference简化
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
一般无限长度的Stream都会配合Stream的limit方法来使用
3.iterate方法,同样生成无限长度的Stream,但它是对给定的种子(seed)反复调用用户指定函数来生成,其生成的元素可认为是:seed,f(seed),f(f(seed))无限循环
Stream.iterate(1, i -> i + 1).limit(10).forEach(System.out::println);
4.concat方法,将两个Stream组合成一个Stream
Stream stream1 = Stream.of(“1”, “2”, “3”);
Stream stream2 = Stream.of(“4”, “5”);
Stream.concat(stream1, stream2);
5.builder方法,使用追加的方式建立Stream然后消费
Stream.builder()
.add(“1”)
.add(“2”)
.build();
2.3 其他方式
Java也在其他可能有Stream操作的类中增加了便捷的方法
// 文件读取
java.io.BufferedReader.lines()
// 文件遍历
java.nio.file.Files.walk()
// 随机数
java.util.Random.ints()
// 正则匹配
Pattern.splitAsStream(java.lang.CharSequence)
- 3.转换Stream
转换Stream就是把一个Stream通过某些行为转换成新的Stream。Stream接口中定义了常用的几个转换方法,但在实际使用中确实大大的方便。
3.1 distinct
对于Stream中包含的元素进行去重(依赖元素的equals方法)
之前对一个List进行去重,都是借助HashSet来做
List list2 = new ArrayList(new HashSet(list));
现在也可以使用distinct来做
List list2 = list.stream().distinct().collect(Collectors.toList());
3.2 filter
对Stream中包含的元素使用给定的过滤函数进行过滤,新生成的Stream只包含符合条件的元素
List<Integer> numbers = Arrays.asList(-1, 1, 0);
numbers.stream().filter(n -> n > 0).collect(Collectors.counting());
3.3 map
对Stream中包含的元素使用给定的转换函数进行转换,新生成的Stream只包含转换生成的元素
Stream.of(1, 2, 3).map(n -> n + 1).collect(Collectors.toList());
这个方法由三个对于原始数据类型的变种方法,分别是mapToInt,mapToLong, mapToDouble, 主要是减少自动装箱和拆箱的性能消耗。
3.4 flatMap
和map相似,但一般用来将多层级扁平化,举个例子大家就清楚了。
有个两层结构的数据结构
[ [1, 2, 3], [4, 5], [6] ]
把它全部摊平
[1, 2, 3, 4, 5, 6]
就可以使用flatMap
Stream<List<Integer>> intStream = Stream.of(Arrays.asList(1, 2, 3),Arrays.asList(4,5),Arrays.asList(6));
intStream.flatMap(childList -> childList.stream()).forEach(System.out::println);
3.5 peek
生成一个包含原Stream所有元素的新Stream,同时提供一个消费函数(Consume),当Stream中每个元素被消费时都会执行给定的消费函数。
举个例子来说,对1到9求和,求和前打印所有求和元素。因为Stream是单向的,做了求和就不能再打印了,怎么办呢?用peek方法。
int sum = IntStream.range(1, 10).peek(System.out::println).sum();
System.out.println("sum:" + sum);
3.6 limit
对Stream进行截断操作,获取其前N个元素,如果原Stream中包含元素个数小于N,就获取其所有元素。
IntStream.range(1, 10).limit(5).forEach(System.out::println);
3.7 skip
丢弃Stream前N个元素,返回剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,返回空Stream。
比如打印一个数组的第10到20个元素
IntStream.range(1, 100).skip(10).limit(10).forEach(System.out::println);
3.8 sorted
对Stream中的元素按默认方式排序或者指定比较器排序
Stream.of(1, 3, 4 ,2).sorted().forEach(System.out::println);
4.聚合Stream
聚合操作接受一个元素序列作为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如求元素的总和或最大值,或者把元素累积成一个List对象。Stream接口有一些通用的汇聚操作,比如reduce和collect;也有特定用途的汇聚操作,比如sum,max,count。
4.1 collect
将Stream中的元素收集到一个结果容器中。java8还为collect方法提供了工具类Collectors,可以方便地生成List,Set,Map等集合。
// 获取userId的List
List<Long> userIdList = users.stream().map(u -> u.getId()).collect(Collectors.toList());
// 获取userId的Set
Set<Long> userIdSet = users.stream().map(u -> u.getId()).collect(Collectors.toSet());
// 获取userId的Map
Map<Long, User> userIdMap = users.stream().collect(Collectors.toMap(u -> u.getId(), u -> u));
另外Collectors还提供了两种非常常用的聚合方式:分组和分片,分别对应groupingBy和partitioningBy两个方法。怎么用呢,我们来看例子。
我想将用户按所在地址分组,北京的放在一起,上海的放在一起,可以用groupingBy。
Map<String, List> userAddressMap = users.stream().collect(Collectors.groupingBy(User::getAddress));
或者我想将用户按年龄区分,20岁以上为一个分片,20岁以下为一个分片,可以用partitioningBy。
Map<Boolean, List> userAgeMap = users.stream().collect(Collectors.partitioningBy(u -> u.getAge() > 20));
这两个的区别也就显而易见了,groupingBy是按照某个内部字段进行分组,而partitioningBy是按照某个条件将元素分成是和非两个分片。
Collectors中还有其他很便捷的方法,有兴趣的可以研究下。
4.2 reduce
上面说的collect可以看成对Stream元素进行不同方式的聚集,而reduce则是对Stream元素进行指定方式的聚合。
比如我想求用户的最大年龄,可以使用reduce来操作。
Optional optional = users.stream().map(User::getAge).reduce((a1, a2) -> a1 > a2 ? a1 : a2);
这个方法返回值是Optional,它是java8防止出现空指针的一种方式。要获得最大的年龄,调用Optional的get方法即可。
Integer maxAge = users.stream().map(User::getAge).reduce((a1, a2) -> a1 > a2 ? a1 : a2).get();
但是这种方式还是可能会出现空指针,我们可以在reduce时设置一个默认值。
Integer maxAge = users.stream().map(User::getAge).reduce(0 ,(a1, a2) -> a1 > a2 ? a1 : a2);
这样就不需要Optional做一次转换了。
像最大,最小,计数这些是常见的计算方式,在Stream中也都提供了直接的方法供我们使用。
max(Comparator comp)
min(Comparator comp)
count()
**对于max,min方法需要提供Compartor比较器。如果是在诸如mapToInt,mapToLong,mapToDouble之类的基础数据操作后,则不用提供比较器,**另外还支持求和(sum)和平均(average)方法。
users.stream().mapToInt(User::getAge).sum();
users.stream().mapToInt(User::getAge).average();
4.3 搜索相关
Stream API还提供了几种快捷的搜索方法,支持在一组元素中的常用搜索。
allMatch:所有元素都满足匹配条件
anyMatch:任一元素满足匹配条件
findFirst:返回Stream中的第一个元素
findAny:返回Stream中的任意个元素
noneMatch:所有元素都不满足匹配条件
4.4 遍历
将Stream中的元素逐个按照指定方式进行消费。
打印所有用户
users.stream().forEach(System.out::println);
5.Stream API的优点
Stream转换操作是惰性化的(lazy),即多次转换操作在聚合操作时只需一次循环就能完成,并不会因为多次转换造成额外的循环开销。
因为1的原因,Stream的数据源可以是无限的,它并不会像普通iterator一样需要把所有数据都加载到内存中。比如Stream的generate方法和limit方法相结合,可以从无限的数据源中操作自己想要的数据。
Stream可以并行化操作,它依赖于java7中引入的Fork/Join框架来拆分任务和加速处理过程。对于Collection子类,直接使用parallelStream方法即可开启并行化操作。不过并行化也是会有额外的开销的,因此要适当地使用。
6.Stream API用例
以库存实体Stock为例
public class Stock {
// 主键id
Long id;
// 商品id
Long skuId;
// 供应商id
int supplierId;
// 状态(0:不可用,1:可用,2:任务中)
int status;
// 库存量
BigDecimal amount;
}
一般都是根据条件从数据库查询出一个Stock的List,变量名为stockList,从这个List出发,介绍一些常用的Stream的用例。
6.1 从大集合中获取小集合
/
/ 获取id的集合
List<Long> idList = stockList.stream().map(Stock::getId).collect(Collectors.toList());
// 获取skuid集合并去重
List<Long> skuIdList = stockList.stream().map(Stock::getSkuId).distinct().collect(Collectors.toList());
// 获取supplierId集合(supplierId的类型为int,返回List<Integer>,使用boxed方法装箱)
Set<Integer> supplierIdSet = stockList.stream().mapToInt(Stock::getSupplierId).boxed().collect(Collectors.toSet());
6.2 分组与分片
// 按skuid分组
Map<Long, List<Stock>> skuIdStockMap = stockList.stream().collect(Collectors.groupingBy(Stock::getSkuId));
// 过滤supplierId=1然后按skuId分组
Map<Long, List<Stock>> filterSkuIdStockMap = stockList.stream().filter(s -> s.getSupplierId() == 1).collect(Collectors.groupingBy(Stock::getSkuId));
// 按状态分为不可用和其他两个分片
Map<Boolean, List<Stock>> partitionStockMap = stockList.stream().collect(Collectors.partitioningBy(s -> s.getStatus() == 0));
6.3 计数与求和
// 统计skuId=1的记录数
long skuIdRecordNum = stockList.stream().filter(s -> s.getSkuId() == 1).count();
// 统计skuId=1的总库存量
BigDecimal skuIdAmountSum = stockList.stream().filter(s -> s.getSkuId() == 1).map(Stock::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
6.4 特定用法
// 多重分组并排序,先按supplierId分组,再按skuId分组,排序规则,先supplierId后skuId
Map<Integer, Map<Long, List<Stock>>> supplierSkuStockMap = stockList.stream().collect(Collectors.groupingBy(Stock::getSupplierId, TreeMap::new,
Collectors.groupingBy(Stock::getSkuId, TreeMap::new, Collectors.toList())));
// 多条件排序,先按supplierId正序排,再按skuId倒序排
// (非stream方法,而是集合的sort方法,直接改变原集合元素,使用Function参数)
stockList.sort(Comparator.comparing(Stock::getSupplierId)
.thenComparing(Stock::getSkuId, Comparator.reverseOrder()));