Java8新特性——StreamAPI

http://www.runoob.com/java/java8-method-references.html

1. 流的基本概念 1.1 什麼是流? 流是Java8引入的全新概念,它用來處理集合中的數據,暫且能夠把它理解爲一種高級集合。 衆所周知,集合操做很是麻煩,若要對集合進行篩選、投影,須要寫大量的代碼,而流是以聲明的形式操做集合,它就像SQL語句,咱們只需告訴流須要對集合進行什麼操做,它就會自動進行操做,並將執行結果交給你,無需咱們本身手寫代碼。 所以,流的集合操做對咱們來講是透明的,咱們只需向流下達命令,它就會自動把咱們想要的結果給咱們。因爲操做過程徹底由Java處理,所以它能夠根據當前硬件環境選擇最優的方法處理,咱們也無需編寫複雜又容易出錯的多線程代碼了。 1.2 流的特色 只能遍歷一次 咱們能夠把流想象成一條流水線,流水線的源頭是咱們的數據源(一個集合),數據源中的元素依次被輸送到流水線上,咱們能夠在流水線上對元素進行各類操做。一旦元素走到了流水線的另外一頭,那麼這些元素就被「消費掉了」,咱們沒法再對這個流進行操做。固然,咱們能夠從數據源那裏再得到一個新的流從新遍歷一遍。 採用內部迭代方式 若要對集合進行處理,則需咱們手寫處理代碼,這就叫作外部迭代。而要對流進行處理,咱們只需告訴流咱們須要什麼結果,處理過程由流自行完成,這就稱爲內部迭代。 1.3 流的操做種類 流的操做分爲兩種,分別爲中間操做 和 終端操做。 中間操做 當數據源中的數據上了流水線後,這個過程對數據進行的全部操做都稱爲「中間操做」。 中間操做仍然會返回一個流對象,所以多箇中間操做能夠串連起來造成一個流水線。 終端操做 當全部的中間操做完成後,若要將數據從流水線上拿下來,則須要執行終端操做。 終端操做將返回一個執行結果,這就是你想要的數據。 1.4 流的操做過程 使用流一共須要三步: 準備一個數據源 執行中間操做 中間操做能夠有多個,它們能夠串連起來造成流水線。 執行終端操做 執行終端操做後本次流結束,你將得到一個執行結果。 2. 流的使用 2.1 獲取流 在使用流以前,首先須要擁有一個數據源,並經過StreamAPI提供的一些方法獲取該數據源的流對象。數據源能夠有多種形式: 集合 這種數據源較爲經常使用,經過stream()方法便可獲取流對象: List<Person> list = new ArrayList<Person>(); Stream<Person> stream = list.stream(); 數組 經過Arrays類提供的靜態函數stream()獲取數組的流對象: String[] names = {"chaimm","peter","john"}; Stream<String> stream = Arrays.stream(names); 值 直接將幾個值變成流對象: Stream<String> stream = Stream.of("chaimm","peter","john"); 文件 try(Stream lines = Files.lines(Paths.get(「文件路徑名」),Charset.defaultCharset())){ //可對lines作一些操做 }catch(IOException e){ } PS:Java7簡化了IO操做,把打開IO操做放在try後的括號中便可省略關閉IO的代碼。
2.2 篩選filter filter函數接收一個Lambda表達式做爲參數,該表達式返回boolean,在執行過程當中,流將元素逐一輸送給filter,並篩選出執行結果爲true的元素。 如,篩選出全部學生: List<Person> result = list.stream() .filter(Person::isStudent) .collect(toList()); 2.3 去重distinct 去掉重複的結果: List<Person> result = list.stream() .distinct() .collect(toList()); 2.4 截取 截取流的前N個元素: List<Person> result = list.stream() .limit(3) .collect(toList()); 2.5 跳過 跳過流的前n個元素: List<Person> result = list.stream() .skip(3) .collect(toList()); 2.6 映射 對流中的每一個元素執行一個函數,使得元素轉換成另外一種類型輸出。流會將每個元素輸送給map函數,並執行map中的Lambda表達式,最後將執行結果存入一個新的流中。 如,獲取每一個人的姓名(實則是將Perosn類型轉換成String類型): List<Person> result = list.stream() .map(Person::getName) .collect(toList()); 2.7 合併多個流 例:列出List中各不相同的單詞,List集合以下: List<String> list = new ArrayList<String>(); list.add("I am a boy"); list.add("I love the girl"); list.add("But the girl loves another girl"); 思路以下: 首先將list變成流: list.stream(); 1 按空格分詞: list.stream() .map(line->line.split(" ")); 2 分完詞以後,每一個元素變成了一個String[]數組。 將每一個String[]變成流: list.stream() .map(line->line.split(" ")) .map(Arrays::stream) 3 此時一個大流裏面包含了一個個小流,咱們須要將這些小流合併成一個流。 將小流合併成一個大流: 用flagmap替換剛纔的map list.stream() .map(line->line.split(" ")) .flagmap(Arrays::stream) 去重 list.stream() .map(line->line.split(" ")) .flagmap(Arrays::stream) .distinct() .collect(toList()); 2.8 是否匹配任一元素:anyMatch anyMatch用於判斷流中是否存在至少一個元素知足指定的條件,這個判斷條件經過Lambda表達式傳遞給anyMatch,執行結果爲boolean類型。 如,判斷list中是否有學生: boolean result = list.stream() .anyMatch(Person::isStudent); 2.9 是否匹配全部元素:allMatch allMatch用於判斷流中的全部元素是否都知足指定條件,這個判斷條件經過Lambda表達式傳遞給anyMatch,執行結果爲boolean類型。 如,判斷是否全部人都是學生: boolean result = list.stream() .allMatch(Person::isStudent); 2.10 是否未匹配全部元素:noneMatch noneMatch與allMatch偏偏相反,它用於判斷流中的全部元素是否都不知足指定條件: boolean result = list.stream() .noneMatch(Person::isStudent); 2.11 獲取任一元素findAny findAny可以從流中隨便選一個元素出來,它返回一個Optional類型的元素。 Optional<Person> person = list.stream() .findAny(); Optional介紹 Optional是Java8新加入的一個容器,這個容器只存1個或0個元素,它用於防止出現NullpointException,它提供以下方法: isPresent() 判斷容器中是否有值。 ifPresent(Consume lambda) 容器若不爲空則執行括號中的Lambda表達式。 T get() 獲取容器中的元素,若容器爲空則拋出NoSuchElement異常。 T orElse(T other) 獲取容器中的元素,若容器爲空則返回括號中的默認值。
ofNullable

2.12 獲取第一個元素findFirst Optional<Person> person = list.stream() .findFirst();
2.13 歸約 歸約是將集合中的全部元素通過指定運算,摺疊成一個元素輸出,如:求最值、平均數等,這些操做都是將一個集合的元素摺疊成一個元素輸出。 在流中,reduce函數能實現歸約。 reduce函數接收兩個參數: 初始值 進行歸約操做的Lambda表達式 2.13.1 元素求和:自定義Lambda表達式實現求和 例:計算全部人的年齡總和 int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge()); 1 reduce的第一個參數表示初試值爲0; reduce的第二個參數爲須要進行的歸約操做,它接收一個擁有兩個參數的Lambda表達式,reduce會把流中的元素兩兩輸給Lambda表達式,最後將計算出累加之和。 2.13.2 元素求和:使用Integer.sum函數求和 上面的方法中咱們本身定義了Lambda表達式實現求和運算,若是當前流的元素爲數值類型,那麼可使用Integer提供了sum函數代替自定義的Lambda表達式,如: int age = list.stream().reduce(0, Integer::sum); 1 Integer類還提供了min、max等一系列數值操做,當流中元素爲數值類型時能夠直接使用。 2.14 數值流的使用 採用reduce進行數值操做會涉及到基本數值類型和引用數值類型之間的裝箱、拆箱操做,所以效率較低。 當流操做爲純數值操做時,使用數值流能得到較高的效率。 2.14.1 將普通流轉換成數值流 StreamAPI提供了三種數值流:IntStream、DoubleStream、LongStream,也提供了將普通流轉換成數值流的三種方法:mapToInt、mapToDouble、mapToLong。 如,將Person中的age轉換成數值流: IntStream stream = list.stream() .mapToInt(Person::getAge); 2.14.2 數值計算 每種數值流都提供了數值計算函數,如max、min、sum等。 如,找出最大的年齡: OptionalInt maxAge = list.stream() .mapToInt(Person::getAge) .max(); 因爲數值流可能爲空,而且給空的數值流計算最大值是沒有意義的,所以max函數返回OptionalInt,它是Optional的一個子類,可以判斷流是否爲空,並對流爲空的狀況做相應的處理。 此外,mapToInt、mapToDouble、mapToLong進行數值操做後的返回結果分別爲:OptionalInt、OptionalDouble、OptionalLong
 
 

 


=======================================================================================

1. 收集器簡介

收集器用來將通過篩選、映射的流進行最後的整理,可使得最後的結果以不一樣的形式展示。
collect方法即爲收集器,它接收Collector接口的實現做爲具體收集器的收集方法。
Collector接口提供了不少默認實現的方法,咱們能夠直接使用它們格式化流的結果;也能夠自定義Collector接口的實現,從而定製本身的收集器。
這裏先介紹Collector經常使用默認靜態方法的使用,自定義收集器會在下一篇博文中介紹。
2. 收集器的使用

2.1 歸約

流由一個個元素組成,歸約就是將一個個元素「摺疊」成一個值,如求和、求最值、求平均值都是歸約操做。
2.1.1 計數

long count = list.stream()
                    .collect(Collectors.counting());


也能夠不使用收集器的計數函數:
long count = list.stream().count();
1
注意:計數的結果必定是long類型。
2.1.2 最值

例:找出全部人中年齡最大的人
Optional<Person> oldPerson = list.stream()
                    .collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));

計算最值須要使用Collector.maxBy和Collector.minBy,這兩個函數須要傳入一個比較器Comparator.comparingInt,這個比較器又要接收須要比較的字段。 
這個收集器將會返回一個Optional類型的值。 
Optional類簡介請移步至:Java8新特性——StreamAPI(一)
2.1.3 求和

例:計算全部人的年齡總和
int summing = list.stream()
                    .collect(Collectors.summingInt(Person::getAge));

固然,既然Java8提供了summingInt,那麼還提供了summingLong、summingDouble。
2.1.4 求平均值

例:計算全部人的年齡平均值
double avg = list.stream()
                    .collect(Collectors.averagingInt(Person::getAge));

注意:計算平均值時,不論計算對象是int、long、double,計算結果必定都是double。
2.1.5 一次性計算全部歸約操做

Collectors.summarizingInt函數能一次性將最值、均值、總和、元素個數所有計算出來,並存儲在對象IntSummaryStatisics中。 
能夠經過該對象的getXXX()函數獲取這些值。
2.1.6 鏈接字符串

例:將全部人的名字鏈接成一個字符串
String names = list.stream()
                        .collect(Collectors.joining());

每一個字符串默認分隔符爲空格,若須要指定分隔符,則在joining中加入參數便可:
String names = list.stream()
                        .collect(Collectors.joining(", "));

此時字符串之間的分隔符爲逗號。
2.1.7 通常性的歸約操做

若你須要自定義一個歸約操做,那麼須要使用Collectors.reducing函數,該函數接收三個參數:
第一個參數爲歸約的初始值
第二個參數爲歸約操做進行的字段
第三個參數爲歸約操做的過程
例:計算全部人的年齡總和
Optional<Integer> sumAge = list.stream()
                                    .collect(Collectors.reducing(0,Person::getAge,(i,j)->i+j));

上面例子中,reducing函數一共接收了三個參數:
第一個參數表示歸約的初始值。咱們須要累加,所以初始值爲0
第二個參數表示須要進行歸約操做的字段。這裏咱們對Person對象的age字段進行累加。
第三個參數表示歸約的過程。這個參數接收一個Lambda表達式,並且這個Lambda表達式必定擁有兩個參數,分別表示當前相鄰的兩個元素。因爲咱們須要累加,所以咱們只需將相鄰的兩個元素加起來便可。
Collectors.reducing方法還提供了一個單參數的重載形式。 
你只需傳一個歸約的操做過程給該方法便可(即第三個參數),其餘兩個參數均使用默認值。
第一個參數默認爲流的第一個元素
第二個參數默認爲流的元素 
這就意味着,當前流的元素類型爲數值類型,而且是你要進行歸約的對象。
例:採用單參數的reducing計算全部人的年齡總和
Optional<Integer> sumAge = list.stream()
            .filter(Person::getAge)
            .collect(Collectors.reducing((i,j)->i+j));

2.2 分組

分組就是將流中的元素按照指定類別進行劃分,相似於SQL語句中的GROUPBY。
2.2.1 一級分組

例:將全部人分爲老年人、中年人、青年人
Map<String,List<Person>> result = list.stream()
                                    .collect(Collectors.groupingby((person)->{
        if(person.getAge()>60)
            return "老年人";
        else if(person.getAge()>40)
            return "中年人";
        else
            return "青年人";
                                    }));

groupingby函數接收一個Lambda表達式,該表達式返回String類型的字符串,groupingby會將當前流中的元素按照Lambda返回的字符串進行分組。 
分組結果是一個Map< String,List< Person>>,Map的鍵就是組名,Map的值就是該組的Perosn集合。
2.2.2 多級分組

多級分組能夠支持在完成一次分組後,分別對每一個小組再進行分組。 
使用具備兩個參數的groupingby重載方法便可實現多級分組。
第一個參數:一級分組的條件
第二個參數:一個新的groupingby函數,該函數包含二級分組的條件
例:將全部人分爲老年人、中年人、青年人,而且將每一個小組再分紅:男女兩組。
Map<String,Map<String,List<Person>>> result = list.stream()
                                    .collect(Collectors.groupingby((person)->{
        if(person.getAge()>60)
            return "老年人";
        else if(person.getAge()>40)
            return "中年人";
        else
            return "青年人";
                                    },
                                    groupingby(Person::getSex)));
此時會返回一個很是複雜的結果:Map< String,Map< String,List< Person>>>。
2.2.3 對分組進行統計

擁有兩個參數的groupingby函數不只僅可以實現多幾分組,還能對分組的結果進行統計。
例:統計每一組的人數
Map<String,Long> result = list.stream()
                                    .collect(Collectors.groupingby((person)->{
        if(person.getAge()>60)
            return "老年人";
        else if(person.getAge()>40)
            return "中年人";
        else
            return "青年人";
                                    },
                                    counting()));

此時會返回一個Map< String,Long>類型的map,該map的鍵爲組名,map的值爲該組的元素個數。
將收集器的結果轉換成另外一種類型

當使用maxBy、minBy統計最值時,結果會封裝在Optional中,該類型是爲了不流爲空時計算的結果也爲空的狀況。在單獨使用maxBy、minBy函數時確實須要返回Optional類型,這樣能確保沒有空指針異常。然而當咱們使用groupingBy進行分組時,若一個組爲空,則該組將不會被添加到Map中,從而Map中的全部值都不會是一個空集合。既然這樣,使用maxBy、minBy方法計算每一組的最值時,將結果封裝在optional對象中就顯得有些多餘。 
咱們可使用collectingAndThen函數包裹maxBy、minBy,從而將maxBy、minBy返回的Optional對象進行轉換。
例:將全部人按性別劃分,並計算每組最大的年齡。
Map<String,Integer> map = list.stream()
                            .collect(groupingBy(Person::getSex,
                            collectingAndThen(
                            maxBy(comparingInt(Person::getAge)),
                            Optional::get
                            )));

此時返回的是一個Map< String,Integer>,String表示每組的組名(男、女),Integer爲每組最大的年齡。 
若是不用collectingAndThen包裹maxBy,那麼最後返回的結果爲Map< String,Optional< Person>>。 
使用collectingAndThen包裹maxBy後,首先會執行maxBy函數,該函數執行完後便會執行Optional::get,從而將Optional中的元素取出來。
2.3 分區

分區是分組的一種特殊狀況,它只能分紅true、false兩組。 
分組使用partitioningBy方法,該方法接收一個Lambda表達式,該表達是必須返回boolean類型,partitioningBy方法會將Lambda返回結果爲true和false的元素各分紅一組。 
partitioningBy方法返回的結果爲Map< Boolean,List< T>>。 
此外,partitioningBy方法和groupingBy方法同樣,也能夠接收第二個參數,實現二級分區或對分區結果進行統計。
相關文章
相關標籤/搜索