1. 程式人生 > >Java 8 學習筆記6——用流收集資料

Java 8 學習筆記6——用流收集資料

流可以用類似於資料庫的操作幫助你處理集合。你可以把Java 8的流看作花哨又懶惰的資料集迭代器。它們支援兩種型別的操作:中間操作(如filtermap)和終端操作(如countfindFirstforEachreduce)。中間操作可以連結起來,將一個流轉換為另一個流。這些操作不會消耗流,其目的是建立一個流水線。與此相反,終端操作會消耗流,以產生一個最終結果,例如返回流中的最大元素。它們通常可以通過優化流水線來縮短計算時間。

之前用過collect終端操作,當時主要是用來把Stream中所有的元素結合成一個List。接下來,你會發現collect是一個歸約操作,就像reduce

一樣可以接受各種做法作為引數,將流中的元素累積成一個彙總結果。具體的做法是通過定義新的Collector介面來定義的,因此區分CollectionCollectorcollect是很重要的。

下面是一些查詢的例子,看看你用collect和收集器能夠做什麼。

  • 對一個交易列表按貨幣分組,獲得該貨幣的所有交易額總和(返回一個Map<Currency,Integer>)。
  • 將交易列表分成兩組:貴的和不貴的(返回一個Map<Boolean,List<Transaction>>)。
  • 建立多級分組,比如按城市對交易分組,然後進一步按照貴或不貴分組(返回一個Map<Boolean,List<Transaction>>
    )。

先來看一個利用收集器的例子。想象一下,你有一個由Transaction構成的List,並且想按照名義貨幣進行分組。在沒有LambdaJava裡,哪怕像這種簡單的用例實現起來都很囉嗦,就像下面這樣:

Map<Currency,List<Transaction>> transactionsByCurrencies=new HashMap<>();	//建立累積交易分組的Map

for(Transaction transaction:transactions){	//迭代Transaction的List
    Currency currency=
transaction.getCurrency(); //提取Transaction的貨幣 List<Transaction> transactionsForCurrency=transactionsByCurrencies.get(currency); if(transactionsForCurrency==null){ //如果分組Map中沒有這種貨幣的條目,就建立一個 transactionsForCurrency=newArrayList<>(); transactionsByCurrencies.put(currency,transactionsForCurrency); } transactionsForCurrency.add(transaction); //將當前遍歷的Transaction加入同一貨幣的Transaction的List }

Streamcollect方法的一個更通用的Collector引數,你就可以用一句話實現完全相同的結果,而用不著使用之前那個toList的特殊情況了:

Map<Currency,List<Transaction>> transactionsByCurrencies
		=transactions.stream().collect(groupingBy(Transaction:: getCurrency));

收集器簡介

上面的例子清楚地展示了函數語言程式設計相對於指令式程式設計的一個主要優勢:你只需指出希望的結果——“做什麼”,而不用操心執行的步驟——“如何做”。在該例子裡,傳遞給collect方法的引數是Collector介面的一個實現,也就是給Stream中元素做彙總的方法。前面的toList只是說“按順序給每個元素生成一個列表”;在本例中,groupingBy說的是“生成一個Map,它的鍵是(貨幣)桶,值則是桶中那些元素的列表”。

要是做多級分組,指令式和函式式之間的區別就會更加明顯:由於需要好多層巢狀迴圈和條件,指令式程式碼很快就變得更難閱讀、更難維護、更難修改。相比之下,函式式版本只要再加上一個收集器就可以輕鬆地增強功能了。

收集器用作高階歸約

剛剛的結論又引出了優秀的函式式API設計的另一個好處:更易複合和重用。收集器非常有用,因為用它可以簡潔而靈活地定義collect用來生成結果集合的標準。更具體地說,對流呼叫collect方法將對流中的元素觸發一個歸約操作(由Collector來引數化)。下圖所示的歸約操作所做的工作和最上面的指令式程式碼一樣。它遍歷流中的每個元素,並讓Collector進行處理。

在這裡插入圖片描述
一般來說,Collector會對元素應用一個轉換函式(很多時候是不體現任何效果的恆等轉換,例如toList),並將結果累積在一個數據結構中,從而產生這一過程的最終輸出。例如,在前面所示的交易分組的例子中,轉換函式提取了每筆交易的貨幣,隨後使用貨幣作為鍵,將交易本身累積在生成的Map中。

如貨幣的例子中所示,Collector介面中方法的實現決定了如何對流執行歸約操作。Collectors實用類提供了很多靜態工廠方法,可以方便地建立常見收集器的例項,只要拿來用就可以了。最直接和最常用的收集器是toList靜態方法,它會把流中所有的元素收集到一個List中:

List<Transaction> transactions=transactionStream.collect(Collectors.toList());

預定義收集器

預定義收集器,也就是那些可以從Collectors類提供的工廠方法(例如groupingBy)建立的收集器。它們主要提供了三大功能:

  • 將流元素歸約和彙總為一個值
  • 元素分組
  • 元素分割槽

歸約和彙總

在需要將流專案重組成集合時,一般會使用收集器(Stream方法collect的引數)。再寬泛一點來說,但凡要把流中所有的專案合併成一個結果時就可以用。這個結果可以是任何型別,可以複雜如代表一棵樹的多級對映,或是簡單如一個整數——也許代表了選單的熱量總和。

先來舉一個簡單的例子,利用counting工廠方法返回的收集器,數一數選單裡有多少種菜:

long howManyDishes=menu.stream().collect(Collectors.counting());

這還可以寫得更為直接:

long howManyDishes=menu.stream().count();

counting收集器在和其他收集器聯合使用的時候特別有用。

在接下來的部分,我們假定你已匯入了Collectors類的所有靜態工廠方法:

import static java.util.stream.Collectors.*;

這樣你就可以寫counting()而用不著寫Collectors.counting()之類的了。

查詢流中的最大值和最小值

讓我們來繼續探討簡單的預定義收集器,看看如何找到流中的最大值和最小值。

假設你想要找出選單中熱量最高的菜。你可以使用兩個收集器,Collectors.maxByCollectors.minBy,來計算流中的最大或最小值。這兩個收集器接收一個Comparator引數來比較流中的元素。你可以建立一個Comparator來根據所含熱量對菜餚進行比較,並把它傳遞給Collectors.maxBy

Comparator<Dish> dishCaloriesComparator
	=Comparator.comparingInt(Dish:: getCalories);

Optional<Dish> mostCalorieDish
	=menu.stream()
    	.collect(maxBy(dishCaloriesComparator));

Java 8引入了Optional,它是一個容器,可以包含也可以不包含值。Optional<Dish>完美地代表了可能返回也可能不返回菜餚的情況。

彙總

另一個常見的返回單個值的歸約操作是對流中物件的一個數值欄位求和。或者你可能想要求平均數。這種操作被稱為彙總操作。讓我們來看看如何使用收集器來表達彙總操作。

Collectors類專門為彙總提供了一個工廠方法:Collectors.summingInt。它可接受一個把物件對映為求和所需int的函式,並返回一個收集器該收集器在傳遞給普通的collect方法後即執行我們需要的彙總操作。舉個例子來說,你可以這樣求出選單列表的總熱量:

int totalCalories=menu.stream().collect(summingInt(Dish:: getCalories));

這裡的收集過程如下圖所示。在遍歷流時,會把每一道菜都對映為其熱量,然後把這個數字累加到一個累加器(這裡的初始值0)。

在這裡插入圖片描述
Collectors.summingLongCollectors.summingDouble方法的作用完全一樣,可以用於求和欄位為longdouble的情況。

但彙總不僅僅是求和;還有Collectors.averagingInt,連同對應的averagingLongaveragingDouble可以計算數值的平均數:

double avgCalories=menu.stream().collect(averagingInt(Dish:: getCalories));

到目前為止,你已經看到了如何使用收集器來給流中的元素計數,找到這些元素數值屬性的最大值和最小值,以及計算其總和和平均值。不過很多時候,你可能想要得到兩個或更多這樣的結果,而且你希望只需一次操作就可以完成。在這種情況下,你可以使用summarizingInt工廠方法返回的收集器。例如,通過一次summarizing操作你可以就數出選單中元素的個數,並得到菜餚熱量總和、平均值、最大值和最小值:

IntSummaryStatistics menuStatistics
		=menu.stream().collect(summarizingInt(Dish:: getCalories));

這個收集器會把所有這些資訊收集到一個叫作IntSummaryStatistics的類裡,它提供了方便的取值(getter)方法來訪問結果。列印menuStatisticobject會得到以下輸出:

IntSummaryStatistics{count=9,sum=4300,min=120,average=477.777778,max=800}

同樣,相應的summarizingLongsummarizingDouble工廠方法有相關的LongSummaryStatisticsDoubleSummaryStatistics型別,適用於收集的屬性是原始型別longdouble的情況。

連線字串

joining工廠方法返回的收集器會把對流中每一個物件應用toString方法得到的所有字串連線成一個字串。這意味著你把選單中所有菜餚的名稱連線起來,如下所示:

String shortMenu=menu.stream().map(Dish:: getName).collect(joining());

請注意,joining在內部使用了StringBuilder來把生成的字串逐個追加起來。此外還要注意,如果Dish類有一個toString方法來返回菜餚的名稱,那你無需用提取每一道菜名稱的函式來對原流做對映就能夠得到相同的結果:

String shortMenu=menu.stream().collect(joining());

二者均可產生以下字串:

porkbeefchickenfrenchfriesriceseasonfruitpizzaprawnssalmon

但該字串的可讀性並不好。幸好,joining工廠方法有一個過載版本可以接受元素之間的分界符,這樣你就可以得到一個逗號分隔的菜餚名稱列表:

String shortMenu=menu.stream().map(Dish:: getName).collect(joining(", "));

正如我們預期的那樣,它會生成:

pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon

廣義的歸約彙總

事實上,我們已經討論的所有收集器,都是一個可以用reducing工廠方法定義的歸約過程的特殊情況而已。

Collectors.reducing工廠方法是所有這些特殊情況的一般化。可以說,先前討論的案例僅僅是為了方便程式設計師而已。例如,可以用reducing方法建立的收集器來計算你選單的總熱量,如下所示:

int totalCalories=menu.stream().collect(reducing(
    							0,Dish:: getCalories,(i,j) -> i+j));

它需要三個引數。

  • 第一個引數是歸約操作的起始值,也是流中沒有元素時的返回值,所以很顯然對於數值和而言0是一個合適的值。
  • 第二個引數就是你在前面“彙總”部分中使用的函式,將菜餚轉換成一個表示其所含熱量的int
  • 第三個引數是一個BinaryOperator,將兩個專案累積成一個同類型的值。這裡它就是對兩個int求和。

同樣,你可以使用下面這樣單引數形式的reducing來找到熱量最高的菜,如下所示:

Optional<Dish> mostCalorieDish
	=menu.stream().collect(reducing(
    		(d1,d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));

你可以把單引數reducing工廠方法建立的收集器看作三引數方法的特殊情況,它把流中的第一個專案作為起點,把恆等函式(即一個函式僅僅是返回其輸入引數)作為一個轉換函式。這也意味著,要是把單引數reducing收集器傳遞給空流的collect方法,收集器就沒有起點;它將因此而返回一個Optional<Dish>物件。

Stream介面的collectreduce方法有何不同?因為兩種方法通常會獲得相同的結果。例如,你可以像下面這樣使用reduce方法來實現toListCollector所做的工作:

Stream<Integer> stream=Arrays.asList(1,2,3,4,5,6).stream();
List<Integer> numbers=stream.reduce(
    					new ArrayList<Integer>(),
    					(List<Integer> l,Integer e) -> {
                            l.add(e);
                            return l;
                        },
    					(List<Integer> l1,List<Integer> l2) -> {
                            l1.addAll(l2);
                            return l1;
                        }
);

這個解決方案有兩個問題:一個語義問題和一個實際問題。語義問題在於,reduce方法旨在把兩個值結合起來生成一個新值,它是一個不可變的歸約。與此相反,collect方法的設計就是要改變容器,從而累積要輸出的結果。這意味著,上面的程式碼片段是在濫用reduce方法,因為它在原地改變了作為累加器的List。以錯誤的語義使用reduce方法還會造成一個實際問題:這個歸約過程不能並行工作,因為由多個執行緒併發修改同一個資料結構可能會破壞List本身。在這種情況下,如果你想要執行緒安全,就需要每次分配一個新的List,而物件分配又會影響效能。這就是collect方法特別適合表達可變容器上的歸約的原因,更關鍵的是它適合並行操作。

  1. 收集框架的靈活性:以不同的方法執行同樣的操作

    你還可以進一步簡化前面使用reducing收集器的求和例子——引用Integer類的sum方法,而不用去寫一個表達同一操作的Lambda表示式。這會得到以下程式:

    int totalCalories=menu.stream().collect(reducing(0,		//初始值
    							Dish:: getCalories,		//轉換函式
    							Integer:: sum));		//累積函式
    

    從邏輯上說,歸約操作的工作原理如下圖所示:利用累積函式,把一個初始化為起始值的累加器,和把轉換函式應用到流中每個元素上得到的結果不斷迭代合併起來。
    在這裡插入圖片描述

    在現實中,我們之前提到的counting收集器也是類似地利用三引數reducing工廠方法實現的。它把流中的每個元素都轉換成一個值為1Long型物件,然後再把它們相加:

    public static <T> Collector<T, ?, Long> counting(){
        return reducing(0L, e -> 1L, Long:: sum);
    }
    

    在這段程式碼片段中的?萬用字元,它用作counting工廠方法返回的收集器簽名中的第二個泛型型別。在這裡,它僅僅意味著收集器的累加器型別未知,換句話說,累加器本身可以是任何型別。我們在這裡原封不動地寫出了Collectors類中原始定義的方法簽名,但在接下來的部分我們將避免使用任何萬用字元表示法,以使討論儘可能簡單。

    還有另一種方法不使用收集器也能執行相同操作——將菜餚流對映為每一道菜的熱量,然後用前一個版本中使用的方法引用來歸約得到的流:

    int totalCalories
    	=menu.stream().map(Dish:: getCalories).reduce(Integer:: sum).get();
    

    請注意,就像流的任何單引數reduce操作一樣,reduce(Integer::sum)返回的不是int而是Optional<Integer>,以便在空流的情況下安全地執行歸約操作。然後你只需用Optional物件中的get方法來提取裡面的值就行了。請注意,在這種情況下使用get方法是安全的,只是因為你已經確定菜餚流不為空。

    一般來說,使用允許提供預設值的方法,如orElseorElseGet來解開Optional中包含的值更為安全。

    最後,更簡潔的方法是把流對映到一個IntStream,然後呼叫sum方法,你也可以得到相同的結果:

    int totalCalories=menu.stream().mapToInt(Dish:: getCalories).sum();
    
  2. 根據情況選擇最佳解決方案

    這再次說明了,函數語言程式設計(特別是Java 8Collections框架中加入的基於函式式風格原理設計的新API)通常提供了多種方法來執行同一個操作。

    這個例子還說明,收集器在某種程度上比Stream介面上直接提供的方法用起來更復雜,但好處在於它們能提供更高水平的抽象和概括,也更容易重用和自定義。

    建議是儘可能為手頭的問題探索不同的解決方案,但在通用的方案裡面,始終選擇最專門化的一個。無論是從可讀性還是效能上看,這一般都是最好的決定。例如,要計選單的總熱量,我們更傾向於最後一個解決方案(使用IntStream),因為它最簡明,也很可能最易讀。同時,它也是效能最好的一個,因為IntStream可以讓我們避免自動拆箱操作,也就是從Integerint的隱式轉換,它在這裡毫無用處。

分組

一個常見的資料庫操作是根據一個或多個屬性對集合中的專案進行分組。就像前面講到按貨幣對交易進行分組的例子一樣,如果用指令式風格來實現的話,這個操作可能會很麻煩、囉嗦而且容易出錯。但是,如果用Java 8所推崇的函式式風格來重寫的話,就很容易轉化為一個非常容易看懂的語句。我們來看看這個功能的第二個例子:假設你要把選單中的菜按照型別進行分類,有肉的放一組,有魚的放一組,其他的都放另一組。用Collectors.groupingBy工廠方法返回的收集器就可以輕鬆地完成這項任務,如下所示:

Map<Dish.Type, List<Dish>> dishesByType
		=menu.stream().collect(groupingBy(Dish:: getType));

其結果是下面的Map

{
    FISH=[prawns,salmon],
    OTHER=[frenchfries,rice,seasonfruit,pizza],
    MEAT=[pork,beef,chicken]
}

這裡,你給groupingBy方法傳遞了一個Function(以方法引用的形式),它提取了流中每一道DishDish.Type。我們把這個Function叫作分類函式,因為它用來把流中的元素分成不同的組。如下圖所示,分組操作的結果是一個Map,把分組函式返回的值作為對映的鍵,把流中所有具有這個分類值的專案的列表作為對應的對映值。在選單分類的例子中,鍵就是菜的型別,值就是包含所有對應型別的菜餚的列表。

在這裡插入圖片描述
但是,分類函式不一定像方法引用那樣可用,因為你想用以分類的條件可能比簡單的屬性訪問器要複雜。例如,你可能想把熱量不到400卡路里的菜劃分為“低熱量”(diet),熱量400700卡路里的菜劃為“普通”(normal),高於700卡路里的劃為“高熱量”(fat)。由於Dish類的作者沒有把這個操作寫成一個方法,你無法使用方法引用,但你可以把這個邏輯寫成Lambda表示式:

public enum CaloricLevel{DIET,NORMAL,FAT}

Map<CaloricLevel,List<Dish>> dishesByCaloricLevel
		=menu.stream().collect(
    		groupingBy(dish -> {
                if(dish.getCalories() <= 400)
                    return CaloricLevel.DIET;
                else if(dish.getCalories() <= 700)
                    return CaloricLevel.NORMAL;
                else
                    return CaloricLevel.FAT;
            })
		);

現在,你已經看到了如何對選單中的菜餚按照型別和熱量進行分組,但要是想同時按照這兩個標準分類怎麼辦呢?分組的強大之處就在於它可以有效地組合。讓我們來看看怎麼做。

多級分組

要實現多級分組,我們可以使用一個由雙引數版本的Collectors.groupingBy工廠方法建立的收集器,它除了普通的分類函式之外,還可以接受collector型別的第二個引數。那麼要進行二級分組的話,我們可以把一個內層groupingBy傳遞給外層groupingBy,並定義一個為流中專案分類的二級標準,如下列程式碼所示。

Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel
	=menu.stream().collect(
    	groupingBy(Dish:: getType,	//一級分類函式
    		groupingBy(dish -> {	//二級分類函式
                if(dish.getCalories() <= 400)
                    return CaloricLevel.DIET;
                else if(dish.getCalories() <= 700)
                    return CaloricLevel.NORMAL;
                else return CaloricLevel.FAT;
            })
        ));

這個二級分組的結果就是像下面這樣的兩級Map

{
	MEAT={DIET=[chicken],NORMAL=[beef],FAT=[pork]},
	FISH={DIET=[prawns],NORMAL=[salmon]},
	OTHER={DIET=[rice,seasonalfruit],NORMAL=[frenchfries,pizza]}
}

這裡的外層Map的鍵就是第一級分類函式生成的值:“fish,meat,other”,而這個Map的值又是一個Map,鍵是二級分類函式生成的值:“normal,diet,fat”。最後,第二級map的值是流中元素構成的List,是分別應用第一級和第二級分類函式所得到的對應第一級和第二級鍵的值:“salmonpizza ”這種多級分組操作可以擴充套件至任意層級,n級分組就會得到一個代表n級樹形結構的nMap

下圖顯示了為什麼結構相當於n維表格,並強調了分組操作的分類目的。
在這裡插入圖片描述

按子組收集資料

在上面,我們看到可以把第二個groupingBy收集器傳遞給外層收集器來實現多級分組。但進一步說,傳遞給第一個groupingBy的第二個收集器可以是任何型別,而不一定是另一個groupingBy。例如,要數一數選單中每類菜有多少個,可以傳遞counting收集器作為groupingBy收集器的第二個引數:

Map<Dish.Type,Long> typesCount=menu.stream().collect(
    	groupingBy(Dish:: getType,counting()));

其結果是下面的Map

{MEAT=3,FISH=2,OTHER=4}

還要注意,普通的單引數groupingBy(f)(其中f是分類函式)實際上是groupingBy(f,toList())的簡便寫法。

再舉一個例子,你可以把前面用於查詢選單中熱量最高的菜餚的收集器改一改,按照菜的型別分類:

Map<Dish.Type,Optional<Dish>> mostCaloricByType
	=menu.stream()
    	.collect(groupingBy(Dish:: getType,
                            maxBy(comparingInt(Dish:: getCalories))));

這個分組的結果顯然是一個map,以Dish的型別作為鍵,以包裝了該型別中熱量最高的DishOptional<Dish>作為值:

{
    FISH=Optional[salmon],
    OTHER=Optional[pizza],
    MEAT=Optional[pork]
}

注意:這個Map中的值是Optional,因為這是maxBy工廠方法生成的收集器的型別,但實際上,如果選單中沒有某一型別的Dish,這個型別就不會對應一個Optional.empty()值,而且根本不會出現在Map的鍵中。groupingBy收集器只有在應用分組條件後,第一次在流中找到某個鍵對應的元素時才會把鍵加入分組Map中。這意味著Optional包裝器在這裡不是很有用,因為它不會僅僅因為它是歸約收集器的返回型別而表達一個最終可能不存在卻意外存在的值。

  1. 把收集器的結果轉換為另一種型別

    因為分組操作的Map結果中的每個值上包裝的Optional沒什麼用,所以你可能想要把它們去掉。要做到這一點,或者更一般地來說,把收集器返回的結果轉換為另一種型別,你可以使用Collectors.collectingAndThen工廠方法返回的收集器,如下所示,查詢每個子組中熱量最高的Dish

    Map<Dish.Type,Dish> mostCaloricByType
    	=menu.stream()
        	.collect(groupingBy(Dish:: getType,		//分類函式
                                collectingAndThen(
                                    maxBy(comparingInt(Dish:: getCalories)),	//包裝後的收集器
                                Optional::get)));	//轉換函式
    

    這個工廠方法接受兩個引數——要轉換的收集器以及轉換函式,並返回另一個收集器。這個收集器相當於舊收集器的一個包裝,collect操作的最後一步就是將返回值用轉換函式做一個對映。在這裡,被包起來的收集器就是用maxBy建立的那個,而轉換函式Optional:: get則把返回的Optional中的值提取出來。前面已經說過,這個操作放在這裡是安全的,因為reducing收集器永遠都不會返回Optional.empty()。其結果是下面的Map

    {FISH=salmon,OTHER=pizza,MEAT=pork}
    

    把好幾個收集器巢狀起來很常見,它們之間到底發生了什麼可能不那麼明顯。下圖可以直觀地展示它們是怎麼工作的。
    在這裡插入圖片描述
    從最外層開始逐層向裡,注意以下幾點。

    • 收集器用虛線表示,因此groupingBy是最外層,根據菜餚的型別把選單流分組,得到三個子流。
    • groupingBy收集器包裹著collectingAndThen收集器,因此分組操作得到的每個子流都用這第二個收集器做進一步歸約。
    • collectingAndThen收集器又包裹著第三個收集器maxBy
    • 隨後由歸約收集器進行子流的歸約操作,然後包含它的collectingAndThen收集器會對其結果應用Optional:get轉換函式。
    • 對三個子流分別執行這一過程並轉換而得到的三個值,也就是各個型別中熱量最高的Dish,將成為groupingBy收集器返回的Map中與各個分類鍵(Dish的型別)相關聯的值。
  2. groupingBy聯合使用的其他收集器的例子

    一般來說,通過groupingBy工廠方法的第二個引數傳遞的收集器將會對分到同一組中的所有流元素執行進一步歸約操作。例如,你還重用求出所有菜餚熱量總和的收集器,不過這次是對每一組Dish求和:

    Map<Dish.Type,Integer> totalCaloriesByType
    	=menu.stream().collect(groupingBy(Dish:: getType,
                                          summingInt(Dish:: getCalories)));
    

    然而常常和groupingBy聯合使用的另一個收集器是mapping方法生成的。這個方法接受兩個引數一個函式對流中的元素做變換另一個則將變換的結果物件收集起來。其目的是在累加之前對每個輸入元素應用一個對映函式,這樣就可以讓接受特定型別元素的收集器適應不同型別的物件。我們來看一個使用這個收集器的實際例子。比方說你想要知道,對於每種型別的Dish,選單中都有哪些CaloricLevel。我們可以把groupingBymapping收集器結合起來,如下所示:

    Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType
    	=menu.stream().collect(
        	
                
               

    相關推薦

    Java 8 學習筆記6——收集資料

    流可以用類似於資料庫的操作幫助你處理集合。你可以把Java 8的流看作花哨又懶惰的資料集迭代器。它們支援兩種型別的操作:中間操作(如filter或map)和終端操作(如count、findFirst、forEach和reduce)。中間操作可以連結起來,將一個流轉換為另一個流。這些操作不會消

    Java 8 學習筆記5——使用

    Streams API可以表達複雜的資料處理查詢。 流讓你從外部迭代轉向內部迭代。這樣,你就用不著寫下面這樣的程式碼來顯式地管理資料集合的迭代(外部迭代)了: List<Dish> vegetarianDishes=new ArrayList<>(); for

    《Java8實戰》-第六章讀書筆記收集資料-01)

    用流收集資料 我們在前一章中學到,流可以用類似於資料庫的操作幫助你處理集合。你可以把Java 8的流看作花哨又懶惰的資料集迭代器。它們支援兩種型別的操作:中間操作(如 filter 或 map )和終端操作(如 count 、 findFirst 、 forEach

    Java 8 in Action》Chapter 6收集資料

    1. 收集器簡介 collect() 接收一個型別為 Collector 的引數,這個引數決定了如何把流中的元素聚合到其它資料結構中。Collectors 類包含了大量常用收集器的工廠方法,toList() 和 toSet() 就是其中最常見的兩個,除了它們還有很多收集器,用來對資料進行對複雜的轉換。 指令式

    Java 8 學習筆記4——的概念

    流是什麼 流是Java API的新成員,它允許你以宣告性方式處理資料集合(通過查詢語句來表達,而不是臨時編寫一個實現)。 就現在來說,你可以把它們看成遍歷資料集的高階迭代器。此外,流還可以透明地並行處理,你無需寫任何多執行緒程式碼了!我們簡單看看使用流的好處吧。 下面兩段程式碼都

    第10篇 java 8----收集資料 -----連線字串

    /** * 連線字串 */ public class Demo02 { public static void main(String[] args) { List<Dish> menues = Arrays.asList(new Dis

    第11篇 java 8----收集資料 -----分組

    ** * 一個常見的資料庫操作是根據一個或多個屬性對集合中的專案進行分組。就像前面講到按貨 * 幣對交易進行分組的例子一樣,如果用指令式風格來實現的話,這個操作可能會很麻煩、囉嗦而 * 且容易出錯。但是,如果用Java 8所推崇的函式式風格來重寫的話,就很容易轉化為一個

    Java 8-Stream API-收集資料

    用指令使風格對交易按照年份分組 @Test public void test9() { //建立根據年份分組的Map Map<Integer,List<Transaction>> t

    Java 8 學習筆記3——Lambda 表示式

    Lambda 表示式簡介 利用行為引數化來傳遞程式碼有助於應對不斷變化的需求。它允許你定義一個程式碼塊來表示一個行為,然後傳遞它。你可以決定在某一事件發生時(例如單擊一個按鈕)或在演算法中的某個特定時刻(例如篩選演算法中類似於“重量超過150克的蘋果”的謂詞,或排序中的自定義比較操作)執

    Java 8 學習筆記2——通過行為引數化傳遞程式碼

    行為引數化就是可以幫助你處理頻繁變更的需求的一種軟體開發模式。一言以蔽之,它意味著拿出一個程式碼塊,把它準備好卻不去執行它。這個程式碼塊以後可以被你程式的其他部分呼叫,這意味著你可以推遲這塊程式碼的執行。例如,你可以將程式碼塊作為引數傳遞給另一個方法,稍後再去執行它。這樣,這個方法的行為就基

    Java 8 學習筆記1——Java 8 概述

    Java 8提供了一個新的API(稱為“流”,Stream),它支援許多處理資料的並行操作,其思路和在資料庫查詢語言中的思路類似——用更高階的方式表達想要的東西,而由“實現”(在這裡是Streams庫)來選擇最佳低階執行機制。這樣就可以避免用synchronized編寫程式碼,這一程式碼不僅

    SDL2.0學習筆記6--SDL播放音訊wave檔案

    #include "SDL.h" struct { Uint8 *sound; // pointer to wave data Uint32 soundlen; // length of wave data int sou

    Java IO ---學習筆記(標準、記憶體讀寫、順序輸入

    1、標準流   語言包 java.lang 中的 System 類管理標準輸入/輸出流和錯誤流。   System.in從 InputStream 中繼承而來,用於從標準輸入裝置中獲取輸入資料(通常是鍵盤)   System.out從 PrintStream 中繼承而來,把輸

    Python 學習筆記之—— sklearn 對資料進行預處理

    1. 標準化 標準化是為了讓資料服從一個零均值和單位方差的標準正態分佈。也即針對一個均值為 m e

    Android學習筆記6-跨程式共享資料-ContentProvider

    1,內容提供器簡介 1,內容提供器(ContentProvider) 主要用於在不同的應用程式之間實現資料共享額功能,它提供了一套完整的機制,允許一個程式訪問另一個程式的資料,同時保證被訪問的資料的安全性。 2,使用內提供器是Android實現跨程式共享資料的標

    第9篇 收集資料----- 歸約和彙總

    /** * 為了說明從Collectors工廠類中能創建出多少種收集器例項,我們重用一下前面的例 * 子:包含一張佳餚列表的選單! * 就像你剛剛看到的,在需要將流專案重組成集合時,一般會使用收集器(Stream方法collect * 的引數)。再寬泛一點來說,但凡要

    caffe學習筆記6--訓練自己的資料

    這一部分記錄下如何用caffe訓練自己的資料集,這裡使用AlexNet的網路結構。 該結構及相應的solver檔案在CAFFE/models/bvlc_alexnet目錄下,使用train_val.prototxt和solver.prototxt兩個檔案 首先,在$CAFF

    6.3(java學習筆記)緩衝

    一、緩衝流   使用緩衝流後的輸入輸出流會先儲存到緩衝區,等緩衝區滿後一次性將緩衝區中的資料寫入或取出。   避免程式頻繁的和檔案直接操作,這樣操作有利於提高讀寫效率。   緩衝流是構建在輸入輸出流之上的,可以理解為是對基本輸入輸出流的增強和擴充套件,但其根本是建立在輸入輸出流之上的。  

    6.5(java學習筆記)其他(位元組陣列,資料,物件,列印

    一、位元組陣列流   之前使用輸入輸出流的操作的物件是檔案,而這裡位元組陣列流操作的物件是記憶體,記憶體可以看做是一個位元組陣列。   使用位元組陣列流讀寫就可以看做是從記憶體A到記憶體B的讀寫,物件時記憶體即位元組陣列。      1.1構造方法     ByteArrayOutputStream

    java學習筆記(6)

    brush 本質 運行 == scanner 私有 定義 bool 調用 java基礎知識: 1:形式參數和返回值的問題 (1)形式參數: 類名:需要該類的對象 抽象類名:需要該類的子類對象 接口名:需要該接口的實現類對象 (2)返回值類型: 類名:返