昨天在 Collection移除元素操做 相关的文章中提到了 Collectors
。相信不少同窗对这个比较感兴趣,那咱们今天就来研究一下 Collectors
。html
Collectors
是 Java 8 加入的操做类,位于 java.util.stream
包下。它会根据不一样的策略将元素收集概括起来,好比最简单经常使用的是将元素装入Map
、Set
、List
等可变容器中。特别对于 Java 8 Stream Api 来讲很是有用。它提供了collect()
方法来对 Stream
流进行终结操做派生出基于各类策略的结果集。咱们就借助于 Stream
来熟悉一下 Collectors
吧。咱们依然用昨天的例子:java
List<String> servers = new ArrayList<>();
servers.add("Felordcn");
servers.add("Tomcat");
servers.add("Jetty");
servers.add("Undertow");
servers.add("Resin");
复制代码
Collectors
提供了一系列的静态方法供咱们使用,一般状况咱们静态导入便可使用。接下来咱们来看看都提供了哪些方法吧。sql
这是一个系列,做用是将元素分别概括进可变容器 List
、Map
、Set
、Collection
或者ConcurrentMap
。数据库
Collectors.toList();
Collectors.toMap();
Collectors.toSet();
Collectors.toCollection();
Collectors.toConcurrentMap();
复制代码
咱们能够根据以上提供的 API 使用 Stream
的 collect
方法中的转换为熟悉的集合容器。很是简单这里再也不演示。api
将元素以某种规则链接起来。该方法有三种重载 joining(CharSequence delimiter)
和 joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix)
安全
// 输出 FelordcnTomcatJettyUndertowResin
servers.stream().collect(Collectors.joining());
// 输出 Felordcn,Tomcat,Jetty,Undertow,Resin
servers.stream().collect(Collectors.joining("," ));
// 输出 [Felordcn,Tomcat,Jetty,Undertow,Resin]
servers.stream().collect(Collectors.joining(",", "[", "]"));
复制代码
用的比较多的是读取 HttpServletRequest
中的 body :app
HttpServletRequest.getReader().lines().collect(Collectors.joining());
复制代码
该方法先执行了一个概括操做,而后再对概括的结果进行 Function
函数处理输出一个新的结果。ide
// 好比咱们将servers joining 而后转成大写,结果为: FELORDCN,TOMCAT,JETTY,UNDERTOW,RESIN
servers.stream.collect(Collectors.collectingAndThen(Collectors.joining(","), String::toUpperCase));
复制代码
按照条件对元素进行分组,和 SQL 中的 group by
用法有殊途同归之妙,一般也建议使用 Java 进行分组处理以减轻数据库压力。groupingBy
也有三个重载方法 咱们将 servers
按照长度进行分组:函数
// 按照字符串长度进行分组 符合条件的元素将组成一个 List 映射到以条件长度为key 的 Map<Integer, List<String>> 中
servers.stream.collect(Collectors.groupingBy(String::length))
复制代码
若是我不想 Map
的 value
为 List
怎么办? 上面的实现实际上调用了下面的方式:spa
//Map<Integer, Set<String>>
servers.stream.collect(Collectors.groupingBy(String::length, Collectors.toSet()))
复制代码
我要考虑同步安全问题怎么办? 固然使用线程安全的同步容器啊,那前两种都用不成了吧! 别急! 咱们来推断一下,其实第二种等同于下面的写法:
Supplier<Map<Integer,Set<String>>> mapSupplier = HashMap::new;
Map<Integer,Set<String>> collect = servers.stream.collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));
复制代码
这就很是好办了,咱们提供一个同步 Map
不就好了,因而问题解决了:
Supplier<Map<Integer, Set<String>>> mapSupplier = () -> Collections.synchronizedMap(new HashMap<>());
Map<Integer, Set<String>> collect = servers.stream.collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));
复制代码
其实同步安全问题 Collectors
的另外一个方法 groupingByConcurrent
给咱们提供了解决方案。用法和 groupingBy
差很少。
partitioningBy
咱们在本文开头的提到的文章中已经见识过了,能够看做 groupingBy
的一个特例,基于断言(Predicate
)策略分组。这里再也不举例说明。
该方法概括元素的的数量,很是简单,再也不举例说明。
这两个方法分别提供了查找大小元素的操做,它们基于比较器接口 Comparator
来比较 ,返回的是一个 Optional
对象。 咱们来获取 servers
中最小长度的元素:
// Jetty
Optional<String> min = servers.stream.collect(Collectors.minBy(Comparator.comparingInt(String::length)));
复制代码
这里其实 Resin
长度也是最小,这里遵循了 "先入为主" 的原则 。固然 Stream.min()
能够很方便的获取最小长度的元素。maxBy
一样的道理。
用来作累加计算。计算元素某个属性的总和,相似 Mysql 的 sum
函数,好比计算各个项目的盈利总和、计算本月的所有工资总和等等。咱们这里就计算一下 servers
中字符串的长度之和 (为了举例不考虑其它写法)。
// 总长度 32
servers.stream.collect(Collectors.summingInt(s -> s.length()));
复制代码
若是咱们对 3.6章节-3.8章节 的操做结果都要怎么办?难不成咱们搞5个 Stream
流吗? 因此就有了 summarizingInt
、summarizingDouble
、summarizingLong
三个方法。 这三个方法经过对元素某个属性的提取,会返回对元素该属性的统计数据对象,分别对应 IntSummaryStatistics
、DoubleSummaryStatistics
、LongSummaryStatistics
。咱们对 servers
中元素的长度进行统计:
DoubleSummaryStatistics doubleSummaryStatistics = servers.stream.collect(Collectors.summarizingDouble(String::length));
// {count=5, sum=32.000000, min=5.000000, average=6.400000, max=8.000000}
System.out.println("doubleSummaryStatistics.toString() = " + doubleSummaryStatistics.toString());
复制代码
结果 DoubleSummaryStatistics
中包含了 总数,总和,最小值,最大值,平均值 五个指标。
该方法是先对元素使用 Function
进行再加工操做,而后用另外一个Collector
概括。好比咱们先去掉 servers
中元素的首字母,而后将它们装入 List
。
// [elordcn, omcat, etty, ndertow, esin]
servers.stream.collect(Collectors.mapping(s -> s.substring(1), Collectors.toList()));
复制代码
有点相似 Stream
先进行了 map
操做再进行 collect
:
servers.stream.map(s -> s.substring(1)).collect(Collectors.toList());
复制代码
这个方法很是有用!可是若是要了解这个就必须了解其参数 BinaryOperator<T>
。 这是一个函数式接口,是给两个相同类型的量,返回一个跟这两个量相同类型的一个结果,伪表达式为 (T,T) -> T
。默认给了两个实现 maxBy
和 minBy
,根据比较器来比较大小并分别返回最大值或者最小值。固然你能够灵活定制。而后 reducing
就很好理解了,元素两两之间进行比较根据策略淘汰一个,随着轮次的进行元素个数就是 reduce
的。那这个有什么用处呢? Java 官方给了一个例子:统计每一个城市个子最高的人。
Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
Map<String, Optional<Person>> tallestByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(BinaryOperator.maxBy(byHeight))));
复制代码
结合最开始给的例子你可使用 reducing
找出最长的字符串试试。
上面这一层是根据 Height
属性找最高的 Person
,并且若是这个属性没有初始化值或者没有数据,颇有可能拿不到结果因此给出的是 Optional<Person>
。 若是咱们给出了 identity
做一个基准值,那么咱们首先会跟这个基准值进行 BinaryOperator
操做。 好比咱们给出高于 2 米 的人做为 identity
。 咱们就能够统计每一个城市不低于 2 米 并且最高的那我的,固然若是该城市没有人高于 2 米则返回基准值identity
:
Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
Person identity= new Person();
identity.setHeight(2.);
identity.setName("identity");
Map<String, Person> collect = persons.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, BinaryOperator.maxBy(byHeight))));
复制代码
这时候就肯定必定会返回一个 Person
了,最起码会是基准值identity
再也不是 Optional
。
还有些状况,咱们想在 reducing
的时候把 Person
的身高先四舍五入一下。这就须要咱们作一个映射处理。定义一个 Function<? super T, ? extends U> mapper
来干这个活。那么上面的逻辑就能够变动为:
Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
Person identity = new Person();
identity.setHeight(2.);
identity.setName("identity");
// 定义映射 处理 四舍五入
Function<Person, Person> mapper = ps -> {
Double height = ps.getHeight();
BigDecimal decimal = new BigDecimal(height);
Double d = decimal.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
ps.setHeight(d);
return ps;
};
Map<String, Person> collect = persons.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, mapper, BinaryOperator.maxBy(byHeight))));
复制代码
今天咱们对 Java 8 中的 Collectors
进行了详细的讲解。若是你熟悉了 Collectors
操做 Stream 会更加驾轻就熟。固然在 Java 8 以后的 Java 9 和 Java 12 中 Collectors
都有新增的功能, 后面有时间咱们会继续进行讲解。敬请关注!
关注公众号:Felordcn获取更多资讯