一塊兒來學Java8(七)——Stream(中)

一塊兒來學Java8(七)——Stream(上)中咱們瞭解到了Stream對象的經常使用方法以及用法。如今一塊兒來深刻了解下Stream.collect()方法的使用java

collect基本用法

collect意思爲收集,它是對Stream中的元素進行收集和概括,返回一個新的集合對象。先來看一個簡單例子:微信

public class CollectTest {

	@Data
	@AllArgsConstructor
	static class Goods {
		private String goodsName;
		private int price;
	}
	
	public static void main(String[] args) {
		List<Goods> list = Arrays.asList(
				new Goods("iphoneX", 4000)
				, new Goods("mate30 pro", 5999)
				, new Goods("redmek20", 2999)
				);
		List<String> nameList = list.stream()
			.map(Goods::getGoodsName)
			.collect(Collectors.toList());
	}

}

在這個例子中,經過map方法返回商品名稱,而後把全部的商品名稱放到了List對象中。app

查看源碼發現,collect方法由兩個重載方法組成。iphone

  • 方法1:
<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);
  • 方法2:
<R, A> R collect(Collector<? super T, A, R> collector);

其中用的最多的是方法2,這個方法能夠看作是方法1的快捷方式,由於Collector中一樣提供了Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner這三個參數,不難猜想其底層仍是要用到方法1對應的實現。ide

咱們能夠先從collect(Collector<? super T, A, R> collector)開始入手,經過這個再去慢慢了解方法1的用法。函數

Collectors

Stream.collect(Collector<? super T, A, R> collector)方法的參數Collector對象主要由Collectors類提供。Collectors類裏面包含了一系列的靜態方法,用來返回Collector對象,經常使用的方法以下列表所示:學習

方法名稱 描述
averagingXX 求平均數
counting 求集合中元素個數
groupingBy 對集合進行分組
joining 對集合元素進行拼接
mapping 可在分組的過程當中再次進行值的映射
maxBy 求最大值
minBy 求最小值
partitioningBy 對元素進行分區
reducing 概括
summarizingXX 彙總
toCollection 轉換成集合對象
toConcurrentMap 轉換成ConcurrentMap
toList 轉換成List
toMap 轉換成Map
toSet 轉換成Set

下面依次來說解下每一個方法的用處。code

averagingXX

averagingXX包括averagingDouble,averagingInt,averagingLong。它們表示求平均值。對象

double averagingInt = Stream.of(1, 2, 3)
		.collect(Collectors.averagingInt(val -> val));
System.out.println("averagingInt:" + averagingInt);

double averagingLong = Stream.of(10L, 21L, 30L)
		.collect(Collectors.averagingLong(val -> val));
System.out.println("averagingLong:" + averagingLong);

double averagingDouble = Stream.of(0.1, 0.2, 0.3)
		.collect(Collectors.averagingDouble(val -> val));
System.out.println("averagingDouble:" + averagingDouble);

它們的參數是一個函數式接口,可使用Lambda表達式編寫,其中Lambda表達式中的參數爲Stream中的元素,返回的是待求平均的數值。下面這則列子是求商品的平均值:接口

List<Goods> list = Arrays.asList(
				new Goods("iphoneX", 4000)
				, new Goods("mate30 pro", 5999)
				, new Goods("redmek20", 2999)
				);
		
double avgPrice = list.stream()
	.collect(Collectors.averagingInt(goods -> goods.getPrice()));
System.out.println("商品的平均價格:" + avgPrice);

summingXX

與averagingXX相似,summingXX方法用來求集合中的元素值的總和。

double summingInt = Stream.of(1, 2, 3)
		.collect(Collectors.summingInt(val -> val));
System.out.println("summingInt:" + summingInt);

double summingLong = Stream.of(10L, 21L, 30L)
		.collect(Collectors.summingLong(val -> val));
System.out.println("summingLong:" + summingLong);

double summingDouble = Stream.of(0.1, 0.2, 0.3)
		.collect(Collectors.summingDouble(val -> val));
System.out.println("summingDouble:" + summingDouble);

打印:

summingInt:6.0
summingLong:61.0
summingDouble:0.6

counting()

counting()返回集合中元素個數。

long count = Stream.of(1,2,3,4,5)
		.collect(Collectors.counting());
System.out.println("count:" + count); // 5

summarizingXX

上面講到了averagingXX(求平均)、summingXX(求和)、counting(求總數),若是我要同時獲取這三個數該怎麼辦呢,能夠用summarizingXX。

IntSummaryStatistics summarizingInt = Stream.of(1, 2, 3)
				.collect(Collectors.summarizingInt(val -> val));
System.out.println("平均值:" + summarizingInt.getAverage());
System.out.println("總個數:" + summarizingInt.getCount());
System.out.println("總和:" + summarizingInt.getSum());
System.out.println("最大值:" + summarizingInt.getMax());
System.out.println("最小值:" + summarizingInt.getMin());

打印:

平均值:2.0
總個數:3
總和:6
最大值:3
最小值:1

summarizingInt將統計結果放到了一個IntSummaryStatistics對象裏面,在對象中能夠獲取不一樣的統計信息。

groupingBy()

groupingBy()是對集合中的元素進行分組,由三個重載方法組成

  • 重載1: groupingBy(Function)
  • 重載2: groupingBy(Function, Collector)
  • 重載3: groupingBy(Function, Supplier, Collector)

其中重載1調用了重載2,重載2調用重載3,所以最終都會執行到重載3中來。

首先看下重載1groupingBy(Function)的用法,這個方法默認分組到新的List中,下面這個例子對商品類型進行分組,一樣的類型的商品放到一個List中。

@Data
@AllArgsConstructor
static class Goods {
	private String goodsName;
	// 類型,1:手機,2:電腦
	private int type;
	@Override
	public String toString() {
		return goodsName;
	}
}

public static void main(String[] args) {
	List<Goods> list = Arrays.asList(
			new Goods("iphoneX", 1)
			, new Goods("mate30 pro", 1)
			, new Goods("thinkpad T400", 2)
			, new Goods("macbook pro", 2)
			);
	
	Map<Integer, List<Goods>> goodsListMap = list.stream()
		.collect(Collectors.groupingBy(Goods::getType));
	goodsListMap.forEach((key, value) -> {
		System.out.println("類型" + key + ":" + value);
	});
}

打印:

類型1:[iphoneX, mate30 pro]
類型2:[thinkpad T400, macbook pro]

上面說到了groupingBy(Function)其實是調用了groupingBy(Function, Collector),其中第二個參數Collector決定了轉換到哪裏,默認是toList(),參見groupingBy(Function)的源碼:

public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }

所以咱們能夠調用groupingBy(Function, Collector)手動指定Collector,假設咱們要把轉換後的元素放到Set當中,能夠這樣寫:

Map<Integer, Set<Goods>> goodsListMap = list.stream()
        .collect(Collectors.groupingBy(Goods::getType, Collectors.toSet()));

查看重載2方法源碼,發現其調用了重載3:

public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }

其中Goods::getType對應classifier,Collectors.toSet()對應downstream。中間那個參數HashMap::new意思很明顯了,即返回的Map的具體實現類是哪一個,若是要改爲LinkedHashMap,能夠這樣寫:

LinkedHashMap<Integer, Set<Goods>> goodsListMap = list.stream()
        .collect(Collectors.groupingBy(Goods::getType, LinkedHashMap::new, Collectors.toSet()));

這正是重載3的使用方式。

Collectors中的groupingByConcurrent方法正是基於重載3而來,中間的代碼改爲了ConcurrentHashMap::new而已。

public static <T, K>
    Collector<T, ?, ConcurrentMap<K, List<T>>>
    groupingByConcurrent(Function<? super T, ? extends K> classifier) {
        return groupingByConcurrent(classifier, ConcurrentHashMap::new, toList());
    }

groupingBy方法中的Collector參數不只僅只能夠toList(),toSet(),它還有更加靈活的用法,以前咱們轉換的都是Map<Integer, List<Goods>>形式,value中存放的是集合對象,若是不想要那麼多屬性,只想要對象裏面的商品名稱,,也就是說咱們想獲得Map<Integer, List<String>>,其中key爲商品類型,value爲商品名稱集合。

這個時候Collectors.mapping()就派上用場了,咱們使用groupingBy(Function, Collector)方法,第二參數傳Collectors.mapping()

Map<Integer, List<String>> goodsListMap = 
list.stream()
	.collect(
		 Collectors.groupingBy(
		    Goods::getType, 
		    Collectors.mapping(Goods::getGoodsName, Collectors.toList())
		 )
	);

mapping()方法有兩個參數,第一參數指定返回的屬性,第二個參數指定返回哪一種集合。

joining

joining方法能夠把Stream中的元素拼接起來。

List<String> list = Arrays.asList("hello", "world");
String str = list.stream().collect(Collectors.joining());
System.out.println(str); // 打印:helloworld

還能夠指定分隔符:

List<String> list = Arrays.asList("hello", "world");
String str = list.stream().collect(Collectors.joining(","));
System.out.println(str); // 打印:hello,world

除此以外,String類提供了一個join方法,功能是同樣的

String str2 = String.join(",", list);
System.out.println(str2);

maxBy&minBy

  • maxBy:找出Stream中最大的元素
@Data
@AllArgsConstructor
static class Goods {
	private String goodsName;
	private int price;
}

public static void main(String[] args) {
	List<Goods> list = Arrays.asList(
			new Goods("iphoneX", 4000)
			, new Goods("mate30 pro", 5999)
			, new Goods("redmek20", 2999)
			);
	
	Goods maxPriceGoods = list.stream()
		.collect(
			Collectors.maxBy(
				Comparator.comparing(Goods::getPrice)
			)
		)
		.orElse(null);
	System.out.println("最貴的商品:" + maxPriceGoods);
}

上面的例子演示了查找最貴的商品,Collectors.maxBy()方法須要傳入一個比較器,須要根據商品的價格來比較。

同理,找到最便宜的商品只需把maxBy替換成minBy便可。

partitioningBy

partitioningBy方法表示分區,它將根據條件將Stream中的元素分紅兩部分,並分別放入到一個Map當中,Map的key爲Boolean類型,key爲true部分存放知足條件的元素,key爲false存放不知足條件的元素。

{
    true -> 符合條件的元素
    false -> 不符合條件的元素
}

partitioningBy方法由兩個重載方法組成

  • 重載1:partitioningBy(Predicate)
  • 重載2:partitioningBy(Predicate, Collector)

其中重載1會調用重載2,所以最終仍是調用了重載2方法,咱們先看下重載1方法。

下面這個例子根據商品類型,將商品劃分爲手機類商品和非手機類商品。

@Data
@AllArgsConstructor
static class Goods {
	private String goodsName;
	// 類型,1:手機,2:電腦
	private int type;
	@Override
	public String toString() {
		return goodsName;
	}
}

public static void main(String[] args) {
	List<Goods> list = Arrays.asList(
			new Goods("iphoneX", 1)
			, new Goods("mate30 pro", 1)
			, new Goods("thinkpad T400", 2)
			, new Goods("macbook pro", 2)
			);
	
	// 手機歸爲一類,非手機商品歸爲一類
	// true -> 手機類商品
	// false -> 非手機類商品
	Map<Boolean, List<Goods>> goodsMap = list.stream()
		.collect(
		    Collectors.partitioningBy(goods -> goods.getType() == 1)
		);
	// 獲取手機類商品
	List<Goods> mobileGoods = goodsMap.get(true);
	System.out.println(mobileGoods);
}

partitioningBy(Predicate, Collector)方法的第二個參數能夠用來指定集合元素,默認使用的List存放,若是要使用Set存放,能夠這樣寫:

Map<Boolean, Set<Goods>> goodsMap = list.stream()
	.collect(
		Collectors.partitioningBy(
			goods -> goods.getType() == 1
			// 指定收集類型
			, Collectors.toSet())
	);

toList & toSet & toCollection

toList和toSet能夠將Stream中的元素轉換成List、Set集合,這是用的比較多的兩個方法。

Stream<Goods> stream = Stream.of(
		new Goods("iphoneX", 4000)
		, new Goods("mate30 pro", 5999)
		, new Goods("redmek20", 2999)
		);

List<Goods> list = stream.collect(Collectors.toList());
Set<Goods> set = stream.collect(Collectors.toSet());

默認狀況下,toList返回的是ArrayList,toSet返回的是HashSet,若是要返回其它類型的集合好比LinkedList,可使用toCollection,它可讓開發者本身指定須要哪一種集合。

LinkedList<Goods> linkedList = stream.collect(Collectors.toCollection(LinkedList::new));

toConcurrentMap

toConcurrentMap方法是將Stream轉換成ConcurrentMap,它由三個重載方法組成

  • 重載1:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
  • 重載2:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
  • 重載3:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)

其中重載1調用重載2,重載2調用重載3,最終都會執行到重載3方法上來。

先看重載1,提供了兩個參數

  • keyMapper:指定ConcurrentMap中的key值
  • valueMapper:指定key對應的value

下面這個例子是將商品的名稱做爲key,價格做爲value

List<Goods> list = Arrays.asList(
		new Goods("iphoneX", 4000)
		, new Goods("mate30 pro", 5999)
		, new Goods("redmek20", 2999)
);
ConcurrentMap<String, Integer> goodsMap = list.stream()
		.collect(
				Collectors.toConcurrentMap(Goods::getGoodsName, Goods::getPrice)
		);
System.out.println(goodsMap);

打印:

{mate30 pro=5999, iphoneX=4000, redmek20=2999}

注意:這個方法要求key不能重複,若是有重複的key,會拋IllegalStateException異常,若是有key重複,須要使用toConcurrentMap(Function, Function, BinaryOperator),即重載2

再來看下重載2:toConcurrentMap(Function, Function, BinaryOperator),這個方法前兩個參數跟重載1同樣,第三個參數用來處理key衝突的狀況,讓開發者選擇一個value值返回。

List<Goods> list = Arrays.asList(
		new Goods("iphoneX", 4000)
		, new Goods("mate30 pro", 5999)
		, new Goods("mate30 pro", 6000) // 這裏有兩個衝突了
		, new Goods("redmek20", 2999)
);
ConcurrentMap<String, Integer> goodsMap = list.stream()
		.collect(
				Collectors.toConcurrentMap(Goods::getGoodsName, Goods::getPrice, new BinaryOperator<Integer>() {
					@Override
					public Integer apply(Integer price1, Integer price2) {
						// 選擇價格貴的返回
						return Math.max(price1, price2);
					}
				})
		);
System.out.println(goodsMap);

打印:{mate30 pro=6000, iphoneX=4000, redmek20=2999}

這個例子中mate30 pro做爲key重複了,在BinaryOperator中,咱們選擇價格高的那一條數據返回。

最後看下重載3,相比於重載2,又多了一個參數Supplier,它可讓開發者指定返回一種ConcurrentMap

重載2調用重載3,默認使用的是ConcurrentMap::new

注意:第四個參數必須是ConcurrentMap或ConcurrentMap的子類

小節

本篇主要講解了Stream.collect的用法,以及Collectors類中靜態方法的使用,在下一篇文章中,咱們將詳細講解關於reduce的相關用法。

按期分享技術乾貨,一塊兒學習,一塊兒進步!微信公衆號:猿敲月下碼

相關文章
相關標籤/搜索