一起来学Java8(七)——Stream(中)

一起来学Java8(七)——Stream(上)中我们了解到了Stream对象的常用方法以及用法。现在一起来深入了解下Stream.collect()方法的使用

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对象中。

查看源码发现,collect方法由两个重载方法组成。

  • 方法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对应的实现。

我们可以先从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

下面依次来讲解下每个方法的用处。

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的相关用法。

欢迎关注作者微信公众号:猿敲月下码,第一时间获得技术分享
微信公众号:猿敲月下码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值