JAVA8新特性Stream的使用方法总结

为什么需要 Stream

Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。

直接开始介绍stream的主要使用的方法

1:map,操作元素将一个stream中的元素经过处理放入另一个stream中

List<CustomerStatisticsDTO> dtos = datas.stream().map(r->ConvertHelper.convert(r, CustomerStatisticsDTO.class)).collect(Collectors.toList());

这个例子中,我们使用了我们经常使用ConvertHelp将datas这个list中每一个节点对象转化为CustomerStatisticsDTO中并放到List这个新的list中,对应我们的java7以前的写法为

List dtos = new ArrayList<>();
for(CustomerStatistics data : datas){
   dtos.add(ConvertHelper.convert(r, CustomerStatisticsDTO.class));
}

瞬间感觉逼格提升了许多,而且使用stream本身的效率也能有所提升

map还有一个好用的地方就是可以方便的取出一个pojo类中的某个属性的集合,例如:

List ids = datas.stream().map(CustomerStatistics::getId).collect(Collectors.toList());

这个方法就是取出datas集合中所有节点的id并且放到ids这个列表中,对应的java7函数为

List ids = new ArrayList<>();
for(CustomerStatistics data : datas){
   ids.add(data.getId());
}

2.forEach,遍历元素进行操作

stream还有一个很好用的函数就是forEach。它可以完美的替代java7的foreach,我们直接上代码

Lock lock = new ReentrantLock();
……
cmd.getContacts().parallelStream.forEach((c) -> {
    lock.lock();
    CustomerContact contact = ConvertHelper.convert(c, CustomerContact.class);
    contact.setCustomerId(cmd.getId());
    contact.setCommunityId(cmd.getCommunityId());
    contact.setNamespaceId(cmd.getNamespaceId());
    contact.setStatus(CommonStatus.ACTIVE.getCode());
    contact.setCustomerSource(cmd.getCustomerSource());
    if(StringUtils.isNotBlank(contact.getName()) && StringUtils.isNotBlank(contact.getPhoneNumber())){
        invitedCustomerProvider.createContact(contact);
    }
    lock.unlock();
});

原来的foreach:

for(CustomerContactDTO dto : cmd.getContacts()){
    CustomerContact contact = ConvertHelper.convert(dto, CustomerContact.class);
    contact.setCustomerId(cmd.getId());
    contact.setCommunityId(cmd.getCommunityId());
    contact.setNamespaceId(cmd.getNamespaceId());
    contact.setStatus(CommonStatus.ACTIVE.getCode());
    contact.setCustomerSource(cmd.getCustomerSource());
    if(StringUtils.isNotBlank(contact.getName()) && StringUtils.isNotBlank(contact.getPhoneNumber())){
        invitedCustomerProvider.createContact(contact);
    }
}

这个forEach写起来看起来与foreach的差别不大,但是不光省略了取dto的步骤,而且使用parallelStream的话可以并行处理list,在复杂逻辑的时候尤其有效,但是注意parallelStream不是线程安全的,所以使用的时候要记得加锁,如果实在不放心,直接使用stream().forEach()也是可以的。

3.filter,通过条件过滤集合元素

filter也是一个很好用的方法,可以根据我们给出的条件在原有的集合中过滤出想要的节点组成新的集合,比如:

List exists = existItemList.stream().filter(r-> r.getFieldId().equals(item.getFieldId()) && r.getDisplayName().equals(item.getItemDisplayName())).collect(Collectors.toList());

这个例子中,existItemList也是List,通过限定条件,我们可以遍历existItemList这个list然后取出符合我们的过滤条件的数组的集合,如果使用java7及以前的方法,我们是这么写的:

List exists = new ArrayList<>();
for(FieldItem item : existItemList){
    if(item.equals(item.getFieldId()) && r.getDisplayName().equals(item.getItemDisplayName())){
        exists.add(item);
    }
}

这个也是可以直观简洁的让我们用一行代码写出6行代码的效果,用起来很舒服。

4.统计中使用stream

stream还有一个重要的意义就是统计,提供了mapToInt方法来帮助我们用更加简洁的代码,写出更高的效率
ps:由于统计是默认返回long的,我这里做了类型转换

data.setTrackingNum(Integer.valueOf(String.valueOf(tempResult.stream().map(StatisticDataDTO::getTrackingNum).collect(Collectors.toList()).stream().mapToInt(x->x).summaryStatistics().getSum())));

这里我用了两次stream的操作,分解一下可以分为以下的操作

被解析的list是tempResult,是一个StatisticDataDTO的链表,第一次操作是从tempResult中取出每一个DTO中的trackingNum属性值,可以写为:
List trackingNums = tempResult.stream().map(StatisticDataDTO::getTrackingNum).collect(Collectors.toList());

然后使用mapToInt
//由于stream的统计是统计成Long型的,但是我接收的DTO中是Integer型的,所以做了一次强转
Long trackingSum = trackingNums.stream().mapToInt(x->x).summaryStatistics().getSum();
data.setTrackingNum(Integer.valueOf(String.valueOf(trackingSum)));

stream效率较高,比foreach要好用不少

Integer trackingSum = 0;
for(StatisticDataDTO tempNote : tempResult){
    trackingSum += tempNote.getTrackingNum();
}

接下来我写了一个测试代码测试效率的,这是统计java8的流统计和我们传统的方法效率的差别,首先是是最简单的相加算法,先试一下1,000,000万个数相加

public static void main(String[] args)
    {
        List filterLists = new ArrayList<>();
        for(int i=0;i<1000000;i++)
        {
            filterLists.add(i);
        }

        Long sum = 0L;
        Date a = new Date();
        for(Integer i : filterLists)
        {
            sum += i;
        }
        System.out.println(sum);
        Date b = new Date();
        sum = 0L;
        Date c = new Date();
        sum = filterLists.stream().mapToInt(x->x).summaryStatistics().getSum();
        System.out.println(sum);
        Date d = new Date();

        long interval = b.getTime()-a.getTime();
        long interval2 = d.getTime()-c.getTime();
        System.out.println("两个时间相差1:"+interval);//
        System.out.println("两个时间相差2:"+interval2);//
    }

499999500000

499999500000

两个时间相差1:21

两个时间相差2:91

此时传统的foreach循环更快

然后将数据量扩大十倍,取10,000,000个数之和

public static void main(String[] args)
    {
        List filterLists = new ArrayList<>();
        for(int i=0;i<10000000;i++)
        {
            filterLists.add(i);
        }

        Long sum = 0L;
        Date a = new Date();
        for(Integer i : filterLists)
        {
            sum += i;
        }
        System.out.println(sum);
        Date b = new Date();
        sum = 0L;
        Date c = new Date();
        sum = filterLists.stream().mapToInt(x->x).summaryStatistics().getSum();
        System.out.println(sum);
        Date d = new Date();

        long interval = b.getTime()-a.getTime();
        long interval2 = d.getTime()-c.getTime();
        System.out.println("两个时间相差1:"+interval);//7251
        System.out.println("两个时间相差2:"+interval2);//307
    }

49999995000000

49999995000000

两个时间相差1:1462

两个时间相差2:98

最后后将数据量再扩大十倍,取100,000,000个数之和

public static void main(String[] args)
    {
        List filterLists = new ArrayList<>();
        for(int i=0;i<100000000;i++)
        {
            filterLists.add(i);
        }

        Long sum = 0L;
        Date a = new Date();
        for(int j=0;j<100000000;j++)
        {
            sum += filterLists.get(j);
        }
        System.out.println(sum);
        Date b = new Date();
        sum = 0L;
        Date c = new Date();
        sum = filterLists.stream().mapToInt(x->x).summaryStatistics().getSum();
        System.out.println(sum);
        Date d = new Date();

        long interval = b.getTime()-a.getTime();
        long interval2 = d.getTime()-c.getTime();
        System.out.println("两个时间相差1:"+interval);//7251
        System.out.println("两个时间相差2:"+interval2);//307
    }

4999999950000000

4999999950000000

两个时间相差1:4386

两个时间相差2:188

虽然在处理数据量不大的集合的时候性能相差无几,但是如果涉及复杂逻辑或者大量计算的话,stream的统计和传统的for循环的时间复杂度不是同一个级别的,所以强烈推荐大家在涉及到统计的部分可以使用stream中的统计,真的效率很不错的。

你可能感兴趣的:(learning,java)