摘要: 使用Java語言遞歸地將Map裏的字段名由駝峯轉下劃線。經過此例能夠學習如何遞歸地解析任意嵌套的List-Map容器結構。php
難度:初級
java
在進行多語言混合編程時,因爲編程規範的不一樣, 有時會須要進行字段名的駝峯-下劃線轉換。好比 php 語言中,變量偏向於使用下劃線,而Java 語言中,變量偏向於駝峯式。當 PHP 調用 java 接口時,就須要將 java 返回數據結構中的駝峯式的字段名稱改成下劃線。使用 jackson 解析 json 數據時,若是不指定解析的類,經常會將 json 數據轉化爲 LinkedHashMap 。 所以,須要將 LinkedHashMap 裏的字段名由駝峯轉爲下劃線。正則表達式
這裏的難點是, Map 結構多是很是複雜的嵌套結構,一個 Map 的某個 key 對應的 value 多是原子的,好比字符串,整數等,多是嵌套的 Map, 也多是含有多個 Map 的 List , 而 map , list 內部又可能嵌套任意的 map 或 list . 所以,要使用遞歸的算法來將 value 裏的 key 遞歸地轉化爲下劃線。算法
首先,要編寫一個基本函數 camelToUnderline,將一個字符串的值從駝峯轉下劃線。這個函數不難,逐字符處理,遇到大寫字母就轉成 _ + 小寫字母 ; 或者使用正則表達式替換亦可;編程
其次,須要使用基本函數 camelToUnderline 對可能多層嵌套的 Map 結構進行轉化。json
假設有一個函數 transferKeysFromCamelToUnderline(amap) , 能夠將 amap 裏的全部 key 從駝峯轉化爲下劃線,包括 amap 裏嵌套的任意 map。返回結果是 resultMap ;數據結構
(1) 首先考慮最簡單的狀況:沒有任何嵌套的狀況,原子類型的 key 對應原子類型的 value ; resultMap.put(newkey, value) 便可 , newkey = camelToUnderline(key);app
(2) 其次考慮 Map 含有嵌套 subMap 的狀況: 假設 <key, value> 中,value 是一個 subMap, 那麼,調用 camelToUnderline(key) 能夠獲得新的 newkey ,調用 transferKeysFromCamelToUnderline(subMap) 就獲得轉換了的 newValue , 獲得 <newkey, newValue>; resultMap.put(newkey, newValue)函數
(3) 其次考慮 Map 含有 List 的狀況: List 裏一般含有多個 subMap , 只要遍歷裏面的 subMap 進行轉換並添加到新的 List, 裏面含有全部轉換了的 newValue = map(transferKeysFromCamelToUnderline, List[subMap]); resultMap.put(newkey, newValue) .學習
當使用遞歸方式思考的時候,有三個技巧值得注意:
(1) 首先,必定從最簡單的狀況開始思考。 這是基礎,也是遞歸終結條件;
(2) 其次,要善於從語義上去理解,而不是從細節上。 好比說 Map 含有嵌套 subMap 的時候, 就不要細想 subMap 裏面是怎樣複雜的結構,是單純的一個子 map ,仍是含有 List 的 Map 的 Map 的 Map; 這樣想會Feng掉滴^_^ 只須要知道 transferKeysFromCamelToUnderline(amap) 可以對任意複雜結構的 amap 進行轉換獲得全部 key 轉換了的 resultMap , 而在主流程中直接使用這個 subResultMap 便可。這個技巧值得多體會多訓練下才能掌握。
(3) 結果的存放。 既能夠放在參數裏,在遞歸調用的過程當中逐步添加完善,也能夠放在返回結果中。代碼實現中展現了兩種的用法。從某種意義來講,遞歸特別須要仔細地設計接口 transferKeysFromCamelToUnderline ,並從接口的語義上去理解和遞歸使用。
/** * Created by shuqin on 16/11/3. * Improved by shuqin on 17/12/31. */ package zzz.study.datastructure.map; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; public class TransferUtil { public static final char UNDERLINE='_'; public static String camelToUnderline(String origin){ return stringProcess( origin, (prev, c) -> { if (Character.isLowerCase(prev) && Character.isUpperCase(c)) { return "" + UNDERLINE + Character.toLowerCase(c); } return ""+c; } ); } public static String underlineToCamel(String origin) { return stringProcess( origin, (prev, c) -> { if (prev == '_' && Character.isLowerCase(c)) { return "" + Character.toUpperCase(c); } if (c == '_') { return ""; } return ""+c; } ); } public static String stringProcess(String origin, BiFunction<Character, Character, String> convertFunc) { if (origin==null||"".equals(origin.trim())){ return ""; } String newOrigin = "0" + origin; StringBuilder sb=new StringBuilder(); for (int i = 0; i < newOrigin.length()-1; i++) { char prev = newOrigin.charAt(i); char c=newOrigin.charAt(i+1); sb.append(convertFunc.apply(prev,c)); } return sb.toString(); } public static void tranferKeyToUnderline(Map<String,Object> map, Map<String,Object> resultMap, Set<String> ignoreKeys) { Set<Map.Entry<String,Object>> entries = map.entrySet(); for (Map.Entry<String, Object> entry: entries) { String key = entry.getKey(); Object value = entry.getValue(); if (ignoreKeys.contains(key)) { resultMap.put(key, value); continue; } String newkey = camelToUnderline(key); if ( (value instanceof List) ) { List newList = buildValueList( (List) value, ignoreKeys, (m, keys) -> { Map subResultMap = new HashMap(); tranferKeyToUnderline((Map) m, subResultMap, ignoreKeys); return subResultMap; }); resultMap.put(newkey, newList); } else if (value instanceof Map) { Map<String, Object> subResultMap = new HashMap<String,Object>(); tranferKeyToUnderline((Map)value, subResultMap, ignoreKeys); resultMap.put(newkey, subResultMap); } else { resultMap.put(newkey, value); } } } public static Map<String,Object> tranferKeyToUnderline2(Map<String,Object> map, Set<String> ignoreKeys) { Set<Map.Entry<String,Object>> entries = map.entrySet(); Map<String,Object> resultMap = new HashMap<String,Object>(); for (Map.Entry<String, Object> entry: entries) { String key = entry.getKey(); Object value = entry.getValue(); if (ignoreKeys.contains(key)) { resultMap.put(key, value); continue; } String newkey = camelToUnderline(key); if ( (value instanceof List) ) { List valList = buildValueList((List)value, ignoreKeys, (m, keys) -> tranferKeyToUnderline2(m, keys)); resultMap.put(newkey, valList); } else if (value instanceof Map) { Map<String, Object> subResultMap = tranferKeyToUnderline2((Map)value, ignoreKeys); resultMap.put(newkey, subResultMap); } else { resultMap.put(newkey, value); } } return resultMap; } public static List buildValueList(List valList, Set<String> ignoreKeys, BiFunction<Map, Set, Map> transferFunc) { if (valList == null || valList.size() == 0) { return valList; } Object first = valList.get(0); if (!(first instanceof List) && !(first instanceof Map)) { return valList; } List newList = new ArrayList(); for (Object val: valList) { Map<String,Object> subResultMap = transferFunc.apply((Map) val, ignoreKeys); newList.add(subResultMap); } return newList; } public static Map<String,Object> generalMapProcess(Map<String,Object> map, Function<String, String> keyFunc, Set<String> ignoreKeys) { Map<String,Object> resultMap = new HashMap<String,Object>(); map.forEach( (key, value) -> { if (ignoreKeys.contains(key)) { resultMap.put(key, value); } else { String newkey = keyFunc.apply(key); if ( (value instanceof List) ) { resultMap.put(keyFunc.apply(key), buildValueList((List) value, ignoreKeys, (m, keys) -> generalMapProcess(m, keyFunc, ignoreKeys))); } else if (value instanceof Map) { Map<String, Object> subResultMap = generalMapProcess((Map) value, keyFunc, ignoreKeys); resultMap.put(newkey, subResultMap); } else { resultMap.put(keyFunc.apply(key), value); } } } ); return resultMap; } }
不管是下劃線轉駝峯,仍是駝峯轉下劃線,須要將兩個字符做爲一個組塊進行處理,根據兩個字符的狀況來判斷和轉化成特定字符。好比下劃線轉駝峯,就是 _x => 到 X, 駝峯轉下劃線,就是 xY => x_y。 採用了前面補零的技巧,是的第一個字符與其餘字符均可以用相同的算法來處理(若是不補零的話,第一個字符就必須單獨處理)。
爲了達到通用性,這裏也使用了函數接口 BiFunction<Character, Character, String> convertFunc ,將指定的兩個字符轉換成指定的字符串。流程仍然是相同的:採用逐字符處理。
細心的讀者會發現一個小BUG,就是當List裏的元素不是Map時,好比 "buyList": ["Food","Dress","Daily"], 程序會拋異常:Cannot cast to java.util.Map。 怎麼修復呢? 須要抽離出個函數,專門對 List[E] 的值作處理,這裏 E 不是 Map 也不是List。這裏不考慮 List 直接嵌套 List 的狀況。
public static List buildValueList(List valList, Set<String> ignoreKeys, BiFunction<Map, Set, Map> transferFunc) { if (valList == null || valList.size() == 0) { return valList; } Object first = valList.get(0); if (!(first instanceof List) && !(first instanceof Map)) { return valList; } List newList = new ArrayList(); for (Object val: valList) { Map<String,Object> subResultMap = transferFunc.apply((Map) val, ignoreKeys); newList.add(subResultMap); } return newList; }
因爲 buildValueList 須要回調tranferKeyToUnderlineX 來生成轉換後的Map,這裏使用了BiFunction<Map, Set, Map> transferFunc。相應的, tranferKeyToUnderline 和 tranferKeyToUnderline2 的列表處理要改爲:
tranferKeyToUnderline: if ( (value instanceof List) ) { List newList = buildValueList( (List) value, ignoreKeys, (m, keys) -> { Map subResultMap = new HashMap(); tranferKeyToUnderline((Map) m, subResultMap, ignoreKeys); return subResultMap; }); resultMap.put(newkey, newList); } tranferKeyToUnderline2: if ( (value instanceof List) ) { List valList = buildValueList((List)value, ignoreKeys, (m, keys) -> tranferKeyToUnderline2(m, keys)); resultMap.put(newkey, valList); }
對於複雜Map結構的處理,寫一遍不容易,若是要作相似處理,是否能夠複用上述處理流程呢? 上述主要的不一樣在於 key 的處理。只要傳入 key 的處理函數keyFunc便可。這樣,當須要從下劃線轉駝峯時,就不須要複製代碼,而後只改動一行了。代碼以下所示,使用了 Java8Map 遍歷方式使得代碼更加簡潔可讀。
public static Map<String,Object> generalMapProcess(Map<String,Object> map, Function<String, String> keyFunc, Set<String> ignoreKeys) { Map<String,Object> resultMap = new HashMap<String,Object>(); map.forEach( (key, value) -> { if (ignoreKeys.contains(key)) { resultMap.put(key, value); } else { String newkey = keyFunc.apply(key); if ( (value instanceof List) ) { resultMap.put(keyFunc.apply(key), buildValueList((List) value, ignoreKeys, (m, keys) -> generalMapProcess(m, keyFunc, ignoreKeys))); } else if (value instanceof Map) { Map<String, Object> subResultMap = generalMapProcess((Map) value, keyFunc, ignoreKeys); resultMap.put(newkey, subResultMap); } else { resultMap.put(keyFunc.apply(key), value); } } } ); return resultMap; }
使用Groovy來對上述代碼進行單測。由於Groovy能夠提供很是方便的Map構造。單測代碼以下所示:
TransferUtils.groovy
package cc.lovesq.study.test import zzz.study.datastructure.map.TransferUtil import static zzz.study.datastructure.map.TransferUtil.* /** * Created by shuqin on 17/12/31. */ class TransferUtilTest { static void main(String[] args) { [null, "", " "].each { assert "" == camelToUnderline(it) } ["isBuyGoods": "is_buy_goods", "feeling": "feeling", "G":"G", "GG": "GG"].each { key, value -> assert camelToUnderline(key) == value } [null, "", " "].each { assert "" == underlineToCamel(it) } ["is_buy_goods": "isBuyGoods", "feeling": "feeling", "b":"b", "_b":"B"].each { key, value -> assert underlineToCamel(key) == value } def amap = ["code": 200, "msg": "successful", "data": [ "total": 2, "list": [ ["isBuyGoods": "a little", "feeling": ["isHappy": "ok"]], ["isBuyGoods": "ee", "feeling": ["isHappy": "haha"]], ], "extraInfo": [ "totalFee": 1500, "totalTime": "3d", "nestInfo": [ "travelDestination": "xiada", "isIgnored": true ], "buyList": ["Food","Dress","Daily"] ] ], "extendInfo": [ "involveNumber": "40", ] ] def resultMap = [:] def ignoreSets = new HashSet() ignoreSets.add("isIgnored") tranferKeyToUnderline(amap, resultMap, ignoreSets) println(resultMap) def resultMap2 = tranferKeyToUnderline2(amap, ignoreSets) println(resultMap2) def resultMap3 = generalMapProcess(amap, TransferUtil.&camelToUnderline, ignoreSets) println(resultMap3) def resultMap4 = generalMapProcess(resultMap3, TransferUtil.&underlineToCamel, ignoreSets) println(resultMap4) } }
本文使用Java語言遞歸地將複雜的嵌套Map裏的字段名由駝峯轉下劃線,並給出了更通用的代碼形式,同時展現瞭如何處理複雜的嵌套結構。複雜的結構老是由簡單的子結構經過組合和嵌套而構成,經過對子結構分而治之,而後使用遞歸技術來組合結果,從而實現對複雜結構的處理。
經過此例,學習了: