写这个文章其实主要是因为刚有个童鞋问了个问题https://segmentfault.com/q/10...
正写的带劲安利Java8
的实现方式,结果还没写完...无意发现问题被关闭了...哎...都写了一半了...又不想放弃,就干脆写成文章
问题主要就是把集合里的数据按照一定大小顺序平均分成若干组的问题,看起来挺简单的,不过我开始看到就想用用stream来实现,但是想了想Collectors里并没有适合的方法,所以就想到了用定制的collector来实现了。
原问题的截图:
正式开始回答(我是直接把之前的回答copy过来的哈):
集合处理的话,我还是推荐Java8
的stream
,题主这个问题设计到分组,那自然就要涉及到stream
的collect
方法了,这个方法是收集数据的意思,该方法的参数就是一个Collector
接口,只要传入一个Collector
的实现类就可以了,常用的实现比如在工具类Collectors
里有toList
,toMap
等,已经帮你默认写了收集为集合或者Map的实现类了,但是明显这些实现类都不合适,所以这里需要定制一个Collector
接口的实现啦
其实就是仿照Collectors
里的内部类CollectorImpl
写一个就是了...
=====================(Collector
介绍,如果你已经清楚可以略过的...)==================
介绍哈Collector
接口的方法,一共5个
Supplier supplier()
BiConsumer accumulator()
BinaryOperator combiner()
Function finisher()
Set characteristics()
方法中有泛型,所以要先要介绍哈Collector
中的三个泛型T, A, R
T
:stream
在调用collect
方法收集前的数据类型A
:A
是T
的累加器,遍历T
的时候,会把T
按照一定的方式添加到A中,换句话说就是把一些T
通过一种方式变成A
R
:R
可以看成是A
的累加器,是最终的结果,是把A
汇聚之后的数据类型,换句话说就是把一些A
通过一种方式变成R
了解了泛型的意思,咱们结合Collectors.toList
构造的默认实现类的实现方式来看看Collector
接口的方法
public static
Collector> toList() {
return new CollectorImpl<>((Supplier>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
官方写的很简单,很随意...
前三个参数分别对应了Collector
的前三个方法,也就是
(Supplier
对应>) ArrayList::new
Supplier supplier()
第一个方法List::add
对应BiConsumer accumulator()
第二个方法(left, right) -> { left.addAll(right); return left; }
对应BinaryOperator combiner()
第三个方法
所以对应着来看就清楚了Supplier supplier()
怎么创建一个累加器(这里对应的是如何创建一个List
)BiConsumer accumulator()
怎么把一个对象添加到累加器中(这里对应的是如何在List
里添加一个对象,当然是调用add
方法咯)BinaryOperator combiner()
怎么把一个累加器和另一个累加器合并起来(这里对应的是如何把List
和List
合并起来,当然是调用addAll
,这里由于最终要返回List,所以A和R是一个类型,都是List所以才调用addAll
)
再来看看第四个方法Function finisher()
,其实就是怎么把A
转化为R
,由于是toList
,所以A
和R
是一样的类型,这里其实用就是Function.identity
最后第五个方法Set
其实就是这个Collector
的一些性质,toList
这里只用了Characteristics.IDENTITY_FINISH
,表示第四个方法可以不用设置,A
类型就是最终的结果
=====================(Collector
介绍完了)==================
现在创建自定义的collector
,类名我就叫NumberCollectorImpl
,由于collector
这里要求有三个泛型,根据题主的需求,这三个泛型只有第一个是未知的,另外两个应该是确认的List
结构,所以写出来应该是这么个效果
static class NumberCollectorImpl implements Collector>, List>>
ok,针对collector
要求实现的5个方法来依次说明
第一个方法Supplier
,很明显应该就是>> supplier()
ArrayList::new
第二个方法BiConsumer
,这个稍微麻烦点,起始应该写成>, T>
(list, item) -> {}
,主要就是补充{}中的代码了
最开始的遍历的时候,这个list
其实是父list
,它肯定是空的,所以这个时候要创建一个新子List
,然后把item
塞进子list
中,最后再把创建的新子list
放入到父list
中
if (list.isEmpty()){
list.add(this.createNewList(item));
}
这里简单封了一个小方法createNewList
,因为待会还要用
private List createNewList(T item){
List newOne = new ArrayList();
newOne.add(item);
return newOne;
}
若父list
不为空,那就要把当前父list
中最后一个子list取出来,若空的话,当然又是要创建一个新子list
然后按照之前的方法做,若不为空,就判断子list
大小咯,若大小超过2,就再次创建一个新子list
然后塞item
,若没有超过就在之前子list
中塞入item
,写出来大概就是这个样子
List last = (List) list.get(list.size() - 1);
if (last.size() < 2){
last.add(item);
}else{
list.add(this.createNewList(item));
}
第三个方法BinaryOperator
,其实就是两个>> combiner()
List
如何合并,当然是addAll
方法
(list1, list2) -> {
list1.addAll(list2);
return list1;
};
第四个方法Function
,由于这个时候A和R的类型一样,都是>, List
>> finisher()
List
,所以这里直接就是>
Function.identity()
啦
最后一个方法Set
这里直接可以按照Collectors.toList
来弄就行了,也就是直接采用Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH))
综上所述,完整代码如下
/**
* 自定义Collector
*
* @author imango
* @since 2017/7/13
*/
public class CustomCollectors {
// 默认采用2个一起分组
public static Collector>, List>> groupByNumber(){
return CustomCollectors.groupByNumber(2);
}
// 根据number的大小进行分组
public static Collector>, List>> groupByNumber(int number){
return new NumberCollectorImpl(number);
}
/**
* 个数分组器
* @param
*/
static class NumberCollectorImpl implements Collector>, List>> {
// 每组的个数
private int number;
public NumberCollectorImpl(int number) {
this.number = number;
}
@Override
public Supplier>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer>, T> accumulator() {
return (list, item) -> {
if (list.isEmpty()){
list.add(this.createNewList(item));
}else {
List last = (List) list.get(list.size() - 1);
if (last.size() < number){
last.add(item);
}else{
list.add(this.createNewList(item));
}
}
};
}
@Override
public BinaryOperator>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}
@Override
public Function>, List>> finisher() {
return Function.identity();
}
@Override
public Set characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
}
private List createNewList(T item){
List newOne = new ArrayList();
newOne.add(item);
return newOne;
}
}
}
外面那个类CustomCollectors
主要是为了封装NumberCollectorImpl
类,以后也可以把其他自定义的收集器实现放在这里面,并对外提供工具方法,并且我在NumberCollectorImpl
类中新增了一个number成员变量,这样就可以自定义分组大小了,CustomCollectors
提供了两个对外方法groupByNumber
,带参数的那个就是可以自定义分组个数的了,没有参数的就是默认按照2个分组了,这样的话,测试写法就是这样
public static void main(String[] args) {
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 按照2个分组
List> twoNumberList = list.stream().collect(CustomCollectors.groupByNumber());
// 按照5个分组
List> fiveNumberList = list.stream().collect(CustomCollectors.groupByNumber(5));
}
这样代码就非常漂亮了~哈哈哈~~