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);
}