Java Stream流使用

SkyEgine發表於2020-12-11

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表示式的熟練使用的話,能夠很大程度上提升我們的開發效率,也能使得程式碼閱讀性和效能方面有一定的提升,作用還是比較大的,在此分享出來,理解比較淺薄,僅算給各位提供一個參考吧,不足之處多多指教。

相關文章