1 场景
相信很多做电商的小伙伴们都会遇到这样一个需求:查出所有的手机集合,然后按照手机的批次字段,对这个集合进行分组。
2 一般的实现方法
手机类:
public class MobilePhone {
/**
* 主键
*/
private String id;
/**
* 手机名称
*/
private String phoneName;
/**
* 手机生产代号
*/
private String phoneCode;
/**
* 品牌
*/
private String brand;
/**
* 类别
*/
private String category;
/**
* 批号
*/
private String batchNo;
/**
* 此处省略get、set方法
*/
}
分组方法:
/**
* 商品list,按照批号分组
* @param mobilePhones 商品List
* @return 分组后的商品
*/
public Map<String,List<MobilePhone>> groupBy(List<MobilePhone> mobilePhones){
Map<String,List<MobilePhone>> phoneMap = new HashMap<>();
for (MobilePhone mobilePhone : mobilePhones) {
List<MobilePhone> phoneList = phoneMap.get(mobilePhone.getBatchNo());
if(phoneList == null){
phoneList = new ArrayList<>();
phoneMap.put(mobilePhone.getBatchNo(),phoneList);
}
phoneList.add(mobilePhone);
}
return phoneMap;
}
顺便说一下,Java8以后,Map
有一个computeIfAbsent
方法,可以对以上方法进行简化:
public Map<String,List<MobilePhone>> groupBy2(List<MobilePhone> mobilePhones){
Map<String,List<MobilePhone>> phoneMap = new HashMap<>();
for (MobilePhone mobilePhone : mobilePhones) {
List<MobilePhone> phoneList =
phoneMap.computeIfAbsent(mobilePhone.getBatchNo(), k -> new ArrayList<>());
phoneList.add(mobilePhone);
}
return phoneMap;
}
3 对方法进行抽象
由于产品的需求会经常改变,譬如说,现在又要求所有的手机按照品牌进行分组?又譬如说,要求手机按照类别进行分组?再者,又要求给电脑进行分组?像上面那样去实现,岂不是要写N个实现方法呢?
下面咱们聊聊,怎么对方法进行抽象。
图中的标号1、2、3处都可以用范型直接进行抽象,问题是标号4处的方法名会经常变化,难以抽象。
猜想:可否在经常变化的方法名外面封装一层固定不变的方法呢?标号4处可以用固定的方法名代替,但是不同的对象仍需要调用特定的方法名去获得属性值,因而可以采用回调函数的思想。
3.1 回调函数
简单的聊一聊回调函数
回调的过程一般由三个角色参与:初始函数(一般称之为主函数)、中间函数、回调函数。
举个生活中的例子进行理解:旅客去酒店住宿,怕早上睡过头,需要酒店老板提供叫醒服务(叫醒服务就是中间函数),但叫醒的方式(叫醒的方式就是回调函数)需由旅客指定,比如打电话叫醒,敲门叫醒,或者怕睡得太死泼冷水叫醒。
伪代码示例:
//回调函数一,打电话叫醒
void phoneWake(Guest g);
//回调函数二,敲门叫醒
void knockWake(Guest g);
//回调函数三,泼冷水叫醒
void waterWake(Guest g);
//中间函数
void wakeService(Guest g,wakeMethod wake){
//中间函数调用回调函数
wake(g);
}
//初始函数(主函数)
main(){
//初始函数中调用中间函数,采用泼冷水的方式叫醒
wakeService(g,waterWake);
}
3.2 具体实现
回到之前的问题,标号4处代码:mobilePhone.getBatchNo()
,由于属性名不同getBatchNo
会经常变化,需要在方法的外面再封装一层名称固定不变的方法。
Java8中有已经定义好的函数接口咱们可以直接拿来用!
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Function
中的apply
方法就是咱们需要的固定不变的方法名,T
就是实例中的MobilePhone
,R
就是getBatch()
返回的String
类型。
在调用groupBy
通用方法的时候,根据场景的不同,去实现Function
函数接口的R apply(T t)
方法,并把它作为参数传进来。例如:
groupBy(phones, new Function<MobilePhone, String>() {
public String apply(MobilePhone mobilePhone){
return mobilePhone.getBatchNo();
}
});
在Java8以后,推荐使用Lambda表达式代替匿名内部类。
/**
* 数据分组的通用方法
* @param list list数据集合
* @param action 函数接口
* @param <T> 分组属性值
* @param <K> 分组数据
* @return 分组结果
*/
public static <T,K> Map<T,List<K>> groupBy(List<K> list,Function<K,T> action){
Map<T,List<K>> map = new HashMap<>();
for (K k : list) {
List<K> groupList = map.get(action.apply(k));
if(groupList == null){
groupList = new ArrayList<>();
map.put(action.apply(k),groupList);
}
groupList.add(k);
}
return map;
}
测试用例:
public static void main(String args[]){
// 这里就假装从数据库中查出了数据哇
List<MobilePhone> phones = new ArrayList<>();
// 利用lambda表达式去实现Function函数接口
Map<String,List<MobilePhone>> groupPhone =
ListUtil.groupBy(phones, e -> e.getBatchNo());
// 下面是lambda表达式的另一种写法
// 编译器会把getBatchNo方法的调用者当作参数传给apply方法,然后得到其方法的返回值
// Map<String,List<MobilePhone>> groupPhone =
// ListUtil.groupBy(phones, MobilePhone::getBatchNo);
}