Collectors 要和 Stream-API 结合起来才能起到效果
前置文章推荐:建议先掌握 stream api 后再来看
《java8 Stream接口的深入解读,stream接口内部的方法你都熟悉吗?》
Collectors 是java.util.stream
下的工具类
因此Collectors的主要用途是收集stream中的元素。
常用的方法有
-
toList()、toSet()
-
toMap(Function, Function)
-
toMap(Function, Function, BinaryOperator)
-
toMap(Function, Function, BinaryOperator, Supplier)
-
toConcurrentMap
与Map一样有3种 -
toCollection(Supplier)
不带参数的toList、toSet
使用方式,流对象.collect(Collectors.toSet())
或者 .collect(Collectors.toList())
重点讲一下带参数的几个方法,这几个方法也是常用的收集方式
1、 toMap(Function, Function)
Map的结构由key 和 value 组成,因此这里的两个Function类型的函数,分表用于获得key和value的
import lombok.Builder;
import lombok.Data;
import java.util.*;
import java.util.stream.Collectors;
class User {
String name;
String sex;
int age;
}
public class Demo {
public static void main(String[] args) {
// 准备测试数据
final ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 3; i++) {
users.add(User.builder().name(String.valueOf(i)).sex("男").age(11).build());
}
// 得到转换后的Map
Map<String, Object> map = users.stream()
.collect(Collectors.toMap(k -> k.name + k.sex , v -> v.age));
// 遍历map
map.forEach((k, v) -> {
System.out.println(k + " " + v);
});
}
}
2、toMap(Function, Function, BinaryOperator)
解决重复key抛异常问题
在key,value的基础上多了一个函数式接口,其作用是为了处理重复key值问题,如果上面两个参数的toMap出现了重复的key值,那么就会抛异常,而新增一个函数式接口的作用就是用于手动处理
BinaryOperator 入参两个value,出参一个
写法可以是(x,y)->z
意思是x是第一个value,y是第二个value值,z则是新的值,可以自己随意更改,但是返回值的类型一定和x,y相同,例如x,y都是字符串则返回的z也是字符串。
import lombok.Builder;
import lombok.Data;
import java.util.*;
import java.util.stream.Collectors;
class User {
String id;
String sex;
int age;
}
public class Demo {
public static void main(String[] args) {
// 准备测试数据
final ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 3; i++) {
users.add(User.builder().id(String.valueOf(i)).sex("男").build());
}
// 得到转换后的Map
Map<String, String> map = users.stream().parallel()
.collect(Collectors.toMap(k -> k.sex, v -> v.id, (x, y) -> {
System.out.println("第一个值的value:"+x);
System.out.println("第二个值的value:"+y);
System.out.println("===========保留:"+y);
return y;
}));
// 遍历map
map.forEach((k, v) -> {
System.out.println("最终结果:" + k + " " + v);
});
}
}
第一个值的value:1
第二个值的value:2
===========保留:2
第一个值的value:0
第二个值的value:2
===========保留:2
最终结果:男 2
3、toMap(Function, Function, BinaryOperator, Supplier)
返回自定义的Map实现类,例如HashMap、ConcurrentHashMap等
最后一个参数一般都是
ConcurrentHashMap::new
HashMap::new
这种形式的
4、toCollection(Supplier)
需要传入一个实现了Collection
接口的实现类的构造方法引用
例如:ArrayList::new
、PriorityQueue::new
Collection接口的实现类可以通过IDEA 的快捷键 Ctrl + H 显示器类继承关系
该接口的实现类太多了,重点关注一些常用的。
会发现 List、Set、Queue(这个需要关注,因为阻塞队列的7种都可以传入),此方法就不深究了,能传入的集合类的实现类太多了
操作流的一些常用方法
1、groupingBy
分组根据传入的方法返回值进行分组
import lombok.Builder;
import lombok.Data;
import java.util.*;
import java.util.stream.Collectors;
class User {
String id;
String sex;
int age;
}
public class Demo {
public static void main(String[] args) {
// 准备测试数据
final ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 3; i++) {
users.add(User.builder().id(String.valueOf(i)).sex("男").age(i*2).build());
users.add(User.builder().id(String.valueOf(i)).sex("女").age(i*2+1).build());
}
// 一个参数的分组,按照字段值进行分组
final Map<String, List<User>> map = users.stream().collect(Collectors.groupingBy(User::getSex));
System.out.println(map);
// 使用2个参数的分组
final Map<String, Set<User>> map2 = users.stream().collect(Collectors.groupingBy(User::getSex,Collectors.toSet()));
System.out.println(map2);
// 使用3个参数的分组
final Map<String, Set<User>> map3 = users.stream().collect(Collectors.groupingBy(User::getSex,TreeMap::new,Collectors.toSet()));
System.out.println(map3);
}
}
{女=[User(id=0, sex=女, age=1), User(id=1, sex=女, age=3), User(id=2, sex=女, age=5)], 男=[User(id=0, sex=男, age=0), User(id=1, sex=男, age=2), User(id=2, sex=男, age=4)]}
{女=[User(id=2, sex=女, age=5), User(id=1, sex=女, age=3), User(id=0, sex=女, age=1)], 男=[User(id=0, sex=男, age=0), User(id=2, sex=男, age=4), User(id=1, sex=男, age=2)]}
{女=[User(id=2, sex=女, age=5), User(id=1, sex=女, age=3), User(id=0, sex=女, age=1)], 男=[User(id=0, sex=男, age=0), User(id=2, sex=男, age=4), User(id=1, sex=男, age=2)]}
2、groupingByConcurrent
并发分组,因并行流下使用groupingBy不会以并行流的方式处理,可以使用这个并发的分组操作
一个参数的groupingByConcurrent
底层是ConcurrentHashMap进行存储
二个参数的groupingByConcurrent
,第二个参数传入自定义的并发Map的实现,注意存在线程安全问题,所以一样要是线程安全的Map
三个参数的groupingByConcurrent
和上面一样,唯一区别groupingBy
进行收集元素的集合必须是线程安全的Map
3、partitioningBy
条件分组,返回值为true的一组,false的另外一组
有2种方法,默认使用ArrayList作为容器分成2组,第二个参数用于改变存储每一组元素的容器
import lombok.Builder;
import lombok.Data;
import java.util.*;
import java.util.stream.Collectors;
class User {
String id;
String sex;
int age;
}
public class Demo {
public static void main(String[] args) {
// 准备测试数据
final ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 3; i++) {
users.add(User.builder().id(String.valueOf(i)).sex("男").age(i*2).build());
users.add(User.builder().id(String.valueOf(i)).sex("女").age(i*2+1).build());
}
// 一个参数的分组,按照字段值进行分组
final Map<Boolean, List<User>> sexMap = users.stream()
.collect(Collectors.partitioningBy(x -> x.sex.equals("女")));
System.out.println(sexMap);
// 使用2个参数的分组,可以传入想要的容器,比如Set
final Map<Boolean, List<User>> sexMap2 = users.stream()
.collect(Collectors.partitioningBy(x -> x.sex.equals("女"),Collectors.toList()));
System.out.println(sexMap2);
}
}
4、joining 调用元素的toString()
方法并且将其连在一起(底层是StringBuilder.append
)
有三种joining
- 无参:没有连接符
- 一个参数:传入一个连接符,中间用这个连接符进行连接,例如:传入
-
则 [A,B]
会变成 A-B
。底层是StringJoiner
- 三个参数:在连接符的基础上,多了首尾连接符。底层是
StringJoiner
,一个参数间接调用了它
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class JoiningDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B", "C");
final String join1 = list.stream().collect(Collectors.joining());
final String join2 = list.stream().collect(Collectors.joining("-"));
final String join3 = list.stream().collect(Collectors.joining("-", "[", "]"));
System.out.println(join1); // ABC
System.out.println(join2); // A-B-C
System.out.println(join3); // [A-B-C]
}
}
ABC
A-B-C
[A-B-C]
5、mapping
mapping(Function<? super T, ? extends U> , Collector<? super U, A, R> )
传入的第一个函数式接口说明
该方法传入的第一个参数是一个Function接口,并且根据砖石表达式得知,入参T
和出参返回值U
是两个不同的类型。
根据流的操作,入参是流的元素对象,而出参只要不是流元素对象即可,比如出参是字符串,数字等其它类型即可。
也即第一个参数只要是x->y
形式即可,x是流中的元素,而y必须不同与x的类型,可以是字符串,x的内部属性,或者数字运算后的结果等其它类型即可
传入的第二个函数式接口说明
第二个参数要求是Collector接口的实现类
,根据IDEA中显示的类继承关系(Ctrl + H
),可以发现只有一个实现类,CollectorImpl是Collectors的内部类
示例1:
class User {
int age;
String sex;
}
public class MappingDemo {
public static void main(String[] args) {
final String collect = Arrays.asList(
User.builder().age(1).sex("男").build(),
User.builder().age(2).sex("女").build(),
User.builder().age(3).sex("男").build()
).stream()
.collect(Collectors.mapping(x -> "年龄:"+x.getAge(),
Collectors.joining(",", "[", "]")));
System.out.println(collect);
}
}
如下是执行的结果。
这里面我们的想法是利用字符串"年龄:"
与 User的属性age进行拼串,成为新的流元素,于是就有了3个字符串。
然后传入第二个参数,进行后续的操作,例如 joining ,就形成了下面的带[]
的最终字符串。
[年龄:1,年龄:2,年龄:3]
第二个参数还可以是其它方式,单一定要是Collector接口的实现类
具体的有
示例2:
第二个参数传toList
public class MappingDemo {
public static void main(String[] args) {
final List<String> list = Arrays.asList(
User.builder().age(1).sex("男").build(),
User.builder().age(2).sex("女").build(),
User.builder().age(3).sex("男").build()
).stream()
.collect(Collectors.mapping(x -> "年龄:" + x.getAge(),
Collectors.toList()));
System.out.println(list);
}
}
打印的结果和上面一样,只不过这里是一个List,而上面是一个完整的字符串
示例3:
传入Collectors.averagingInt
求平均年龄
public class MappingDemo {
public static void main(String[] args) {
final Double avg = Arrays.asList(
User.builder().age(1).sex("男").build(),
User.builder().age(2).sex("女").build(),
User.builder().age(3).sex("男").build()
).stream()
.collect(Collectors.mapping(x -> x.getAge(),
Collectors.averagingInt(x -> x)));
System.out.println("年龄的平均值:"+avg);
}
}
6、collectingAndThen
收集后,对收集结果进行后续的操作,实际上用处不大。
public class MappingDemo {
public static void main(String[] args) {
final Integer sumAge男 = Arrays.asList(
User.builder().age(1).sex("男").build(),
User.builder().age(2).sex("女").build(),
User.builder().age(3).sex("男").build()
).stream()
// 根据性别分组,然后对分成的两组数据进行操作。注意分组返回的是一个Map,如果使用toList那么意思就是对后续的List进行后续操作
.collect(Collectors.collectingAndThen(Collectors.groupingBy(User::getSex), x -> {
final List<User> 男 = x.get("男");// 得到男性的所有User
final List<User> 女 = x.get("女");
return 男.stream().collect(Collectors.summingInt(User::getAge));//求男性的年龄总和
}));
System.out.println(sumAge男);// 打印男性年龄总和4
}
}
7、counting、minBy、maxBy
counting:计算流元素个数
最大最小需要元素之间可以比较,故需要传入比较器
minBy:求最小值
maxBy:求最大值
求最大值最小值意义不大,因为Stream本身就提供了max,min。
唯一可能有用到的地方就是对分组后的数据求最值
public class MappingDemo {
public static void main(String[] args) {
final Optional<User> collect = Arrays.asList(
User.builder().age(1).sex("男").build(),
User.builder().age(2).sex("女").build(),
User.builder().age(3).sex("男").build()
).stream()
.collect(Collectors.maxBy((x1, x2) -> x1.getAge() - x2.getAge()));
// 实际上调用stream的max或者min即可,这种写法反而麻烦。
// 暂时举不出一个好一点的例子
System.out.println(collect.get());// 找出年龄最大的
}
}
8、求和summingInt、summingLong、summingDouble
public class MappingDemo {
public static void main(String[] args) {
final IntSummaryStatistics statistics = Arrays.asList(
User.builder().age(1).sex("男").build(),
User.builder().age(2).sex("女").build(),
User.builder().age(3).sex("男").build()
).stream()
.collect(Collectors.summarizingInt(x -> x.getAge()));
System.out.println("年龄的总:" + statistics);
}
}
9、平均数averagingInt、averagingLong、averagingDouble
这个方法在5、示例3中有用到
使用方法很简单,就是流元素的类型要和对应的类型匹配即可。
public class MappingDemo {
public static void main(String[] args) {
final Double avg = Arrays.asList(
User.builder().age(1).sex("男").build(),
User.builder().age(2).sex("女").build(),
User.builder().age(3).sex("男").build()
).stream()
.collect(Collectors.averagingInt(User::getAge));
System.out.println(avg);
}
}
10、归约reducing
,有合并的含义
例如:A,B,C
reduce操作就相当于 A和B进行操作得到一个结果Z,然后Z和C进行操作得到最终结果。
11、统计summarizingInt、summarizingLong、summarizingDouble
Int、Long、Double主要区别在于平均值可能是小数,以及长度的限制
返回元素个数、总和、最小值、最大值、平均值
public class MappingDemo {
public static void main(String[] args) {
final IntSummaryStatistics collect = Arrays.asList(
User.builder().age(1).sex("男").build(),
User.builder().age(2).sex("女").build(),
User.builder().age(3).sex("男").build()
).stream()
.collect(Collectors.summarizingInt(User::getAge));
System.out.println(collect);
}
}
IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000, max=3}