Java Stream流使用

What - 是什么

  Stream 是JDK8 引入的一个 流 的概念,这个流不同于 IO中输入和输出流,这个流主要作用是将数组和集合由非Stream流对象转换成Stream对象,这个类在java.util.stream.Stream下,主要操作符分为三种:获取流、中间操作、终止操作。
    Stream 给人的感觉就是一种用SQL语句从数据库查询数据的方式对Java集合运算、排序、过滤等操作的高阶操作。
 以下来自官方解释:
 Stream(流)是一个来自数据源的元素队列并支持聚合操作
    1、 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
    2、 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
    3、 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

 和以前的Collection操作不同, Stream操作还有两个基础的特征:
    1、 Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
    2、 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

Why - 为什么

概念了解了一堆,但是也没说为啥我们要用Stream? 下面就来说下从我个人而言感觉为啥要使用Stream!

效率提升

    没有使用Stream之前,我们对集合的操作往往都是通过循环来做的,有时候是为了取值,有时候为了将符合判断条件的数据提取出来,重新生成一个新的集合,有时候也是为了统计整个集合中某个字段和、平均值等。但使用Stream后,往往这些需要定义若干变量,做若干循环的操作往往就变得只有一句话的事情。
    
for example.

```
// 将集合中所有发票订单提取出并用,隔开
StringBuffer stringBuffer = new    StringBuffer();
for(Invoice invoice : invoiceList){       
   stringBuffer.append(invoice.getOrderId()).append(",");
}
if(ObjectUtil.isNotEmpty(stringBuffer)){    
   System.out.println("orderIds:"+stringBuffer.toString());
}

```
使用Stream操作

```
String collect = 
invoiceList.stream().map(Invoice::getOrderId).collect(Collectors.joining(","));
System.out.println("collect:"+collect);
```
又或者是 为了将符合条件的数据提取出来,单独添加到新集合中,并根据金额字段倒序排序,最后将数据主键订单号取出

    List<Invoice> collectInvoiceList = new LinkedList<>();
    for(Invoice invoice : invoiceList){             
     if("10000001".equals(invoice.getCompanyId())){    
        collectInvoiceList.add(invoice);   
     }
   }
   Collections.sort(collectInvoiceList, new Comparator<Invoice>(){    
   public int compare(Invoice t1, Invoice t2){       
     return t2.getTotalAmount().compareTo(t1.getTotalAmount());    
   }
   });
   List<String> orderIdList = new LinkedList<>();
   for(Invoice invoice : collectInvoiceList){    
     orderIdList.add(invoice.getOrderId());
   }
 如果使用Stream又该是怎么样呢?
 
    List<String> collectOrderIdList = invoiceList.stream().filter(invoice -> 
        "10000001".equals(invoice.getCompanyId()))              .sorted(Comparator.comparing(Invoice::getTotalAmount).reversed())        .map(Invoice::getOrderId).collect(Collectors.toList());

代码简洁

    代码简洁程度应该也不用我多说啥了,在上面代码中也可以看到不少Lambda变成方式的风格的。的确在使用Stream的时候如果不加点Lambda 的确会感觉好像少了点意思,就是没有灵魂。开玩笑了,更准确的原因是因为这两者天然就应该结合使用,Stream里面的很多方法本来就是支持函数式编程,所以在使用Stream过程中,如果本来就对函数式编程有一定的了解的话,更会如鱼得水。

代(bi)码(ge)易(gao)懂

    大家想必都对阿里Java编程规范有一定的了解,在Idea里面如果安装了这个插件对编码过程中也会有一定的提示,而其中有一条推荐的规范就是单个方法代码不要超过80行,如果我们在对集合或数据的操作都是使用for循环来处理,代码的行数就会必不可免变多,而如果for循环多,并且中间再夹杂触目惊心的if/else分支,那这个代码可能一周之内自己懂,一年之后上帝懂了。所以使用Stream流也会在很多时候能够让我们的代码更容易让人理解。

How - 怎么用

    上面已经说到了Stream是什么以及为什么要使用Stream,但是具体怎么使用且听我缓缓道来。。。
    
    首先是关于Stream的操作符,主要分为“获取流”、“中间操作”、“终止操作”,三种操作符主要作用分别是: 
    1、将非Stream对象转换为Stream对象;
    2、对Stream进行排序、过滤、转换、去重等操作;
    3、对处理过的数据进行收集或消费;
 Stream首先是一项懒加载的操作,只有用到的时候才会去真正执行,如下:
 
 // 加载Stream流并将所有数除以0
 Stream<Integer> integerStream = Stream.of(new Integer[]{1, 2, 3, 4, 5, 6, 0}).map(x -> x / 0);
System.out.println("=================分割===============");
这段代码直接运行,是不会报错的,因为这时候创建的Stream流根本没有具体使用,但如果换成下面这段代码就会运行报错

// 加载Stream流并将所有数除以0S
tream<Integer> integerStream = Stream.of(new Integer[]{1, 2, 3, 4, 5, 6, 
0}).map(x -> x / 0);
System.out.println("=================分割===============");
List<Integer> integerList = integerStream.collect(Collectors.toList());
这时候直接执行的话会报 
Exception in thread "main" java.lang.ArithmeticException: / by zero 这个异常。
Stram只有真正用到的时候才会去执行。

获取流

Java中获取流的方法无非几种,如下:
    // 通过集合获取Stream
    Collection.stream();  
    Collection.parallelStream();
    // 通过数组获取Stream
    String [] orderIdArr = new String[]{"hello","world","nice","to","meet","you"};
    Stream<String> stream = Arrays.stream(orderIdArr);
    或者:
    Stream<String> orderIdStream = Stream.of(orderIdArr);
上述重点就是通过各种方式能够获取到Stream对象,归根结底,Stream的类型有两种,一种是Stream ,另一种是parallelStream. 两者的区别主要Stream是单线程,parallelStream是多线程并且异步。
从个人角度而言,推荐Stream流而非parallelStream 流。原因是因为如果使用parallelStream的话线程安全、线程消费都是需要考虑的问题,并且如果一旦处理的重量级比较大,耗时很长,甚至可能会导致其他执行的任务没有线程资源可以使用,相对而言容易造成死锁等情况也比较高。
当然如果处理的数据对线程安全不关心,仅仅因为处理数据比较大,需要提高处理速度的话,那使用parallelStream是没有问题的。

中间操作符

 中间操作符相对而言内容会比较多,因为基本涵盖了我们目前日常开发中能够所有用到的中间操作,包括map(转换)、limit(限流)、distinct(去重)、filter(过滤)、sorted(排序)等,这里面就只列出我平时用的比较多的地方,具体更多细节的话,还需要自己去研究。
filter-过滤
filter的官方介绍大概就是 将需要包含的元素过滤出。从我个人的理解而言,我认为filter等同于sql语句中的where条件,主要作用就是字面意思过滤。从我们项目中而言,我们很多时候为了过滤出一个集合中公司ID等同于某个值的发票数据,这个时候用filter就是一个很好用的方式,说不如写,具体看代码中是怎么做的吧。

List<String> customerCodeList = orderMainAdvancedList.stream().filter(orderMainAdvanced -> StrUtil.isNotEmpty(orderMainAdvanced.getCustomerCode()))   .map(OrderMainAdvanced::getCustomerCode).distinct().collect(Collectors.toList());
上面这段代码主要为了实现的功能是从订单列表中将所有客户编号不为空的订单过滤出来,然后将所有的客户编号取出后去重,最后返回一个String的List集合。如果使用常规的for循环判断,光这段代码需要做一个循环,并且至少需要两个对象参与到循环中,而使用filter可以帮我比较轻松的就做到这一步。
map-转换
map的使用可以分为四种,一是直接使用map;二是mapToDouble,三是mapToInt,四是mapToLong;
首先说一些比较简单点的,就是mapToDouble,mapToInt,mapToLong ,这三个的用法基本是一样,可以将List中某个对象的某个Double值、Int值、Long值取出转换并可以对取出的List做收集然后根据需要取值。比如这样一个情况,我们需要将所有商品明细金额取出后取平均值。
double asDouble = orderDetailAdvancedList.stream().mapToDouble(OrderDetailAdvanced::getAmount).average().getAsDouble();
这段代码其实就是为了实现上述功能,但是这一步其实也是有一定的提前的,因为如果最后不使用getAsDouble()这个方法的话,仅仅到average() 直接返回的话,会返回一个Optional对象(JDK8新特性之一),通过Optional对象我们可以玩一些更花的操作或者说实现一些更优雅的判断。具体在此不多做描述,需要的可以自行查阅研究JDK8 新特性之Optional.不想多写重复的功能,所以这里就不介绍mapToInt和mapToLong的操作,因为这三者我认为基本操作概念都是一样的,没什么大差距。
map()这个方法就很有意思了,内部参数你可以认为是一个函数式编程,怎么使用的话其实介绍filter的时候已经代码里面写到了,就是map(OrderMainAdvanced::getOrderId) ,这仅仅是一个比较常规的使用,还有一些操作比如给予一个List<String> ,内部对象全是小写字母,需要你实现将所有字符串全部转为大写的,JDK8之前我们会怎么写呢,这里简单列下伪代码:
// 定义准备存储大写字母的List
List<String> upperStrList = new LinkedList<String>();
// 循环遍历原List
// 使用str.toUpperCase后的字符串加入新List
而如果使用map的话,就可以很简单 原List.stream().map(String::toUpperCase)
除此之外其实还有比如需要计算一个数字集合或数组所有元素阶乘、平方等等。这里仅仅做抛砖引玉,更多操作还是自己参悟吧,我说完估计也忘完了。。。
distinct-去重
    distinct就是为了去重,和sql中的distinct基本都是一致的,因为我们使用filter或map等操作符之后,很有可能在返回的集合中还是会有重复的值,而这结果并非是我们想要的,我们想要的很多时候是去重过后的数据集,distinct就是起到这样的一个作用,可以帮我们做去重操作。代码的话filter中也用到了这个写法,
    orderMainAdvancedList.stream().filter(orderMainAdvanced -> StrUtil.isNotEmpty(orderMainAdvanced.getCustomerCode()))   .map(OrderMainAdvanced::getCustomerCode).distinct()
    这个目前我只发现这一个常规操作,如果有更多用法的话可以给点建议。
limit-限流
limit这个操作sql中想必大家也都是比较熟悉了,就是为了限流,这个也没太多需要介绍的,根据实际需求对做过一定处理的集合或数组限制部分待处理的数据,这个目前我只在请求接口的时候用到,因为担心一次性请求太多数据导致一些神奇的问题,所以使用limit限制了每次处理数量,简单直接上代码invoiceList.stream().map(Invoice::getOrderId).limit(50)
sorted-排序
sorted 这个也是比较熟悉的一个操作符,类似于sql中的order by ,但我感觉这个排序在我们实际开发中还是很有意思的,这个不仅仅是stream特有的内容,但是如果能够熟练使用这个方法的话,在开发中提升效率还是比较高的,sorted这个方法可以不传参,那就是以自然序排序,当然也可以传入Comparator相关的一些方法,实现一定的自定义排序,比如我们对商品明细行号进行排序,可以比较简单的写出来:
invoiceList.stream().sorted(Comparator.comparing(Invoice::getLineNo)),至于为啥需要对商品明细行号排序就不说了,实际业务需求,在使用这个方法的时候根据实际需要吧。当然这个正序排序,如果需要倒序排序呢,其实也比较简单:
invoiceList.stream().sorted(Comparator.comparing(Invoice::getLineNo).reversed())
在比较后直接调用reversed()方法实现倒序排序。这个排序也只是一个简单写法,当然可以根据需要自定义实现排序。
如果说想要和sql一样呢,多个字段排序呢?? 其实也是一样可以做,这里的Comparator也是可以支持多字段排序的,至于具体怎么做,在此不做过多赘述,简单提一个词 thenComparing,想要了解或者实际需要的可以自行研究。

终止操作符

说完中间操作符就讲到终止操作了,终止操作主要是为了对数据的收集或消费的,所有的终止操作符只能有一个并且使用终止操作符之后就不能在对流进行更多的操作了。
终止操作符主要包括collect(收集)、count(统计)、findFirst(查找第一个)、anyMatch(任何一个匹配)、noneMatch(全不匹配)、allMatch(全匹配)、min(最小值)、max(最大值)、average(平均值)、forEach(遍历操作),这里列出来的也还是目前我使用的比较多的一些操作,具体全部的使用还是自己研究吧。

关于终止操作符的操作就从简单的开始说起吧,复杂点的应用的话需要说的内容会比较多,就放在后面说,目前列出的也仅仅是我目前开发中比较常用的一些方法,可能不够全面,在此也只能说是起个头,更多操作还是自己研究摸索吧。

count-统计
count方法在一定程度上和sql中count的用法或实现的功能是一样,就是为了统计在中间操作处理完后,累计的数量,返回的是一个long型,其实也和数组的length属性以及集合的size()方法差不多,只不过是count是针对于处理后的Stream数据进行统计,没什么好说的,简单举个例子就知道了:
long countInvoice = invoiceList.stream().filter(invoiceQueryVO -> 
StrUtil.isNotBlank(invoiceQueryVO.getMachineNo())).count();
findFirst-查找第一个
findFirst 的作用我感觉和平时在使用sql查询时,查询出是一个List集合,非空判断后,我们一般会使用get(0) 这样一种写法,findFirst也是这样一个概念,区别在于findFirst()返回的是一个Optional对象,如果需要返回具体的T类型,需要再调用get()方法具体获取值,如果对Optional比较熟练的情况下的话其实还是比较推荐使用Optinal的,毕竟这个是可以号称解决所有的NPE的,简单列下代码吧:
InvoiceQueryVO invoiceExist = new InvoiceQueryVO();
InvoiceQueryVO invoiceQueryVO1 = invoiceList.stream().filter(invoiceQueryVO -> 
    StrUtil.isNotBlank(invoiceQueryVO.getMachineNo())).findFirst().orElse(invoiceExist);
anyMatch/noneMatch/allMatch-匹配
anyMath/noneMatch/allMatch 这三个方法目前我实际只使用到了anyMatch和noneMatch,从方法名称来解释意思,anyMatch就是判断处理完的Stream中是否有任意一个匹配指定规则的数据,具体项目中我使用是判断某个商品明细List中是否有负数明细,代码如下:
boolean result = minusDetailList.stream().anyMatch(orderDetailAdvanced -> 
 customerRule.getTotalDiscountProductName().equals(orderDetailAdvanced.getGoodsName()));
 返回的值是一个boolean类型的,如果不是这样写的话,我们可能需要做一次循环操作,每一次都需要判断才能,时间复杂度有比较大的区别,如果中间不对每个数据进行转换处理,Stream的count的时间复杂度是O(1),而for循环处理是O(n),从这个程度上而言的话,使用Stream的一些方法是一定程度上可以提升我们的效率的。
min/max/average/sum - 取值/计算
这三个方法也其实是最基础的一些用法,将Stream流经过处理后,然后根据实际需求调用对应方法获取处理数据的最小值/最大值/平均值,需要注意的是如果待处理的流在准备使用上述方法是非数值类型的,average 和sum 是没法使用的,并且min和max方法也需要自己重新实现排序方法的。
invoiceList.stream().mapToDouble(InvoiceQueryVO::getNoTaxMoney).average();
invoiceList.stream().mapToDouble(InvoiceQueryVO::getNoTaxMoney).sum();
forEach - 遍历消费
forEach方法也是JDK8 新出的一个特性,可以直接对Collection集合直接做循环遍历,这个方法从使用角度而言的话结合Lambda可以更优雅的使用,比如在对集合操作完后,需要输出其中某个字段:
invoiceList.stream().map(InvoiceQueryVO::getInvoiceNum).forEach(System.out::println);
这是一个比较简单的方式,还有的操作就是在forEach中单独加入自定的函数式编程,可以根据自己实际来操作,简单列下我目前使用的一个方法吧,目的是将过滤出来的数据再次处理,拼接成需要返回的报文数据
splitOrderList.forEach(splitOrder -> {    
    if(CodeConstants.Order.DOC_STATUS_OPENED == splitOrder.getDocStatus()){       
    // 订单已开票        
    InvoiceExample invoiceExample = new InvoiceExample();        
    invoiceExample.createCriteria().andCompanyIdEqualTo(splitOrder.getCompanyId()).andOrderIdLike(splitOrder.getOrderId()+"%");        
    List<Invoice> invoices = invoiceMapper.selectByExample(invoiceExample);             
        if(!invoices.isEmpty()){            
        // 发票非空时,拼接已开发票报文数据            
        dealInvoicedOrder(orderMain.getSelfOrderId(), invoiceQueryVOList,invoices);       
         }   
   }
});
collect-收集(五星级重要)
最重要的就放在最后面再说了,collect方法和后续的一个reduce方法是Stream流里面最重要的,也相对而言使用难度和场景也会更多
collect 中能够使用的方法相对而言是比较多的,首先常用的就是Collectors.toList(),目的是为了将Stream流处理好的数据返回到另一个List中,当然不仅仅是List ,其他的也可以toSet,toMap等,这个接下来会说到。简单来说,collect中使用Collectors可以帮我们完成很多事情比如:分组、排序、最小值、最大值、平均值等,一般来说我们通过sql可以完成的一些聚合相关的操作,使用Collectors基本都可以完成;
  • Collectors.toSet()

     将需要的元素信息处理完后,将返回值转换为Set。
     Set<String> buyerNameSet = 
         invoices.stream().map(Invoice::getBuyerName).collect(Collectors.toSet());
    
  • Collectors.toMap()

      Collectors.toMap 有点需要注意的地方,这次阿里Java开发规范中也提到了这一点,就是转换map的时候需要注意
      buyerTaxList.forEach(buyerTax -> {    
          Map<Boolean, CustomerRule> collectRuleMap = 
          customerRuleList.stream().distinct().collect(Collectors.toMap(customerRule -> 
          buyerTax.equals(customerRule.getCustomerTax()), Function.identity()));
      });
      Map<String, CustomerRule> customerRuleMap = 
      customerRuleList.stream().distinct().collect(Collectors.toMap(customerRule -> 
      customerRule.getCustomerTax(), Function.identity()));
    

Java开发规范中规定如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-366v6Er4-1607665544301)(en-resource://database/668:1)]

     对于这块的规定,我的理解是在使用toMap方法之前需要确认作为map的键值必须是非重复的,如果重复的,在处理之前自定义处理完,也就是我上面代码中为什么使用distinct()的原因。除此之外,toMap的方法其实传参分为三种传参,对应的参数个数是1,2,3 ,目前我上述代码中仅仅展现了使用两种参数的方法,对应三种参数    
  • Collectors.groupingBy(function)

     Collectors.groupingBy()方法接收的也是一个函数,主要作用就是将处理好的Stream通过某个设定的方法函数来进行分组,大概意思和sql中的group by 是一样的,这个处理我感觉还是比较有用的,在处理某些需要分组的数据处理上可以节省比较大的时间,比如,如果给定一个 用户发票集合,需要根据购方名称分组,用这个该怎么写呢,比较简单:
    

    //使用Collectors.groupingBy()

     Map<String, List<Invoice>> groupByBuyer = 
      invoices.stream().collect(Collectors.groupingBy(Invoice::getBuyerName));
    

    // before JDK8

     Map<String,List<Invoice>> buyerInvoiceMap = new LinkedHashMap<>(16);
     for(Invoice invoice : invoices){    
     if(buyerInvoiceMap.containsKey(invoice.getBuyerName())){
          List<Invoice> tempInvoiceList = buyerInvoiceMap.get(invoice.getBuyerName()); 
          tempInvoiceList.add(invoice);    
     }else {
       List<Invoice> tempInvoiceList = new LinkedList<>();        
       tempInvoiceList.add(invoice);               
       buyerInvoiceMap.put(invoice.getBuyerName(),tempInvoiceList);    
       }
     }
    

    这里只展示一种用法了,感兴趣的可以自己多实际操作看看

  • Collectors.counting()

     counting()的主要功能就是为了统计元素的个数,和groupingBy()结合使用的时候,效果还是比较不错的,类似于sql中的count和group方法,比如我们需要将发票信息做一定的条件过滤后,根据公司分组,并统计分组的元素个数:
     Map<String, Long> collect = invoiceList.stream().filter(invoiceQueryVO -> 
      StrUtil.isNotEmpty(invoiceQueryVO.getSelfOrderId()))        .collect(Collectors.groupingBy(InvoiceQueryVO::getCompanyId, Collectors.counting()));
      System.out.println("collect:"+collect.toString());
    
    当然这里除了使用Collectors.counting()外也可以使用其他的Collectors方法,比如minBy,maxBy,mapping
    
  • Collectors.summarizingInt()

          Collectors.summarizing 一共有三个方法,分别是summarizingDouble、summarizingInt、    summarizingLong ,三个方法的作用也和方法名直接等同,这类的方法返回值是一个统计类,对应的返回值分值是 DoubleSummaryStatistics、IntSummaryStatistics、LongSummaryStatistics,统计类里面有一些常用的方法,比如 getAverage()、getCount、getMin、getMax、getSum,主要就是对返回的统计数据分别获取对应的具体数值数据,在Collectors里面也有类似的简单实现的方法,比如getSum()方法可以等同于Collectors.summingDouble,getCount()和Collectors.counting()方法作用也一致的,所以可以根据各自实际的需求针对性的返回具体的结果。
    
reduce- 减少(待补充)
    reduce字面上的意思是减少,在stream中reduce方法做的也正是相同的操作,根据一定的规则将Stream中的元素进行计算后返回一个唯一的值。
    在之前说过的方法中,比如min、max 等方法其实都是通过reduce实现的,只不过因为这些方法比较常见并且通用,所以重新封装了单独的方法。
    reduce方法一共有三个override类,分别接收1,2,3个参数,分别如下:
    1、Optional<T> reduce(BinaryOperator<T> accumulator);
        对Stream中的数据通过累加器accumulator迭代计算,最终得到一个Optional对象
    2、T reduce(T identity, BinaryOperator<T> accumulator);
        给定一个初始值identity,通过累加器accumulator迭代计算,得到一个同Stream中数据同类型的结果
    3、<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
给定一个初始值identity,通过累加器accumulator迭代计算,得到一个identity类型的结果,第三个参数用于使用并行流时合并结果

    上述的解释是找得一个博客[(Java8 Stream reduce操作)](https://blog.csdn.net/weixin_41835612/article/details/83687078"%3Ehttps://blog.csdn.net/weixin_41835612/article/details/83687078)  里面的说法,关于这块的内容因为在实际代码中还没有大量使用,所以对此我也没有准备,这块的内容我自己理解也比较肤浅,就不在此献丑了,在此做一个抛砖引玉吧,下面的代码是我自己每一个都单据跑过的代码,也有一定的理解,其实对于reduce方法,接收一个参数、或者两个参数还是比较好理解,但是第三个参数 combine 作用,是不太好理解的一个参数,而且从目前我看到现在为止,第三个参数实际上是仅仅针对于parallelStream中才会起到比较重要的东西,后续待自己理解比较深刻了,会再次补充更新该篇记录
package com.aisino;

import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * @Author: Qjx
 * @Description: ReduceTest
 * @Date:Create:in 2020-05-07 18:15
 * @Modified By:Qjx
 */
public class ReduceTest {

    public void reduceTest() {
        List<Integer> list = Lists.newArrayList();

        /*Optional accResult = Stream.of(1, 2, 3, 4)*/
        Optional accResult = list.stream()
                .reduce((acc, item) -> {
                    System.out.println("acc : " + acc);
                    acc += item;
                    System.out.println("item: " + item);
                    System.out.println("acc+ : " + acc);
                    System.out.println("--------");
                    return acc;
                });
        System.out.println(accResult.get());
    }
    
    第一段代码,主要作用是将Stream中int元素,通过迭代器的方式累加,这是一个参数的reduce方法,reduce接收一个方法的时候,方法内部是接收两个参数,默认第一个作为初始值,第二个作为累加值,运算的时候,将第一个元素和第二个元素累计和返回作为第二次循环的初始值,并与第三个元素相加,重复执行,直到所有元素处理完返回结果,该方法返回的是一个Optional,可以同时判断下空指针的问题

    public void reduceTest1() {
        int accResult = Stream.of(1, 2, 3, 4)
                .reduce(100, (acc, item) -> {
                    System.out.println("acc : " + acc);
                    acc += item;
                    System.out.println("item: " + item);
                    System.out.println("acc+ : " + acc);
                    System.out.println("--------");
                    return acc;
                });
        System.out.println(accResult);
    }
    
    第二段代码接收两个参数,区别于一个参数在于给定了一个初始值。计算的时候首先以给定的参数值为计算,和Stream中所有元素进行累加,计算后的结果返回,区别于第一个方法,两个参数的reduce返回值和给定参数是一样的,不需要判断空指针问题。
    
    public void reduceTest2() {
        ArrayList<Integer> accResult_ = Stream.of(2, 3, 4)
                .reduce(Lists.newArrayList(1),
                        (acc, item) -> {
                            acc.add(item);
                            System.out.println("item: " + item);
                            System.out.println("acc+ : " + acc);
                            System.out.println("BiFunction");
                            return acc;
                        }, (acc, item) -> {
                            System.out.println("BinaryOperator");
                            acc.addAll(item);
                            System.out.println("item: " + item);
                            System.out.println("acc+ : " + acc);
                            System.out.println("--------");
                            return acc;
                        }
                );
        System.out.println("accResult_: " + accResult_);
    }
}

第三段代码场景暂时没有想到该怎么说,待补充吧。

结尾

关于JDK8 新特性之Stream 的用法暂时就介绍到这里了,总的来说在实际代码开发中感觉Stream和Lambda表达式的熟练使用的话,能够很大程度上提升我们的开发效率,也能使得代码阅读性和性能方面有一定的提升,作用还是比较大的,在此分享出来,理解比较浅薄,仅算给各位提供一个参考吧,不足之处多多指教。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值