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<TestDTO> dtos = datas.stream().map(r->ConvertHelper.convert(r, TestDTO.class)).collect(Collectors.toList());

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

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

而且使用stream本身的效率也能有所提升。

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

List<Long> ids = datas.stream().map(Test::getId).collect(Collectors.toList());

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

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

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

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

Lock lock = new ReentrantLock();
……
cmd.getContacts().parallelStream.forEach((c) -> {
    lock.lock();
    Contact contact = ConvertHelper.convert(c, Contact.class);
    contact.setCustomerId(cmd.getId());
    if(StringUtils.isNotBlank(contact.getName()) && StringUtils.isNotBlank(contact.getPhoneNumber())){
        Provider.createContact(contact);
    }
    lock.unlock();
});

原来的foreach:

for(TestDTO dto : cmd.getContacts()){
    Contact contact = ConvertHelper.convert(dto, Contact.class);
    contact.setCustomerId(cmd.getId());
    if(StringUtils.isNotBlank(contact.getName()) && StringUtils.isNotBlank(contact.getPhoneNumber())){
        Provider.createContact(contact);
    }
}

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

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

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

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

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

List<Item> exists = new ArrayList<>();
for(Item 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(DataDTO::getTrackingNum).collect(Collectors.toList()).stream().mapToInt(x->x).summaryStatistics().getSum())));

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

被解析的list是tempResult,是一个DataDTO的链表,第一次操作是从tempResult中取出每一个DTO中的trackingNum属性值,可以写为:
List<Integer> trackingNums = tempResult.stream().map(DataDTO::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(DataDTO tempNote : tempResult){
    trackingSum += tempNote.getTrackingNum();
}

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

public static void main(String[] args)
    {
        List<Integer> 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<Integer> 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<Integer> 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中的统计,真的效率很不错的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

powerfuler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值