java8新特性,可以用简洁高效的代码来实现一些数据处理。

利用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()));

参考文档:
https://my.oschina.net/u/2377110/blog/1573456

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值