許嵩新歌《放肆》發布 && 遞歸 + Stream+Lambda相遇成樹
一、《放肆》如約而至
今早5:00在迷迷糊糊中醒來,打開手機一看,許嵩又發新歌了,名字叫做《放肆》,澎湃的旋律,依舊古典高雅的用詞,這個大男孩,已經不像12年那時候發些傷感非主流的歌曲了,86年出生的他,除了依舊留著亘古不變的長劉海髮型,心理儼然住進了一個老靈魂。
再小酣一會兒,6:40鬧鈴響起,穿衣洗刷梳頭,便匆匆忙忙的騎著小單車,趕去了地鐵站,重複著之前每天的境況。地鐵站人擠人,稀薄的空氣被每個人儘力吮吸著,每個人在微不足道的間隙里騰出手來,拿著手機忙活著自己的事,人與人的物理距離看起來很近,但每個人彷彿都被丟進了一個叫做螢幕世界的地方,心理距離遠的不著邊際。我望著一個個陌生的面孔,放棄了看電子書的想法,帶上了耳機微閉雙眼,開始循環播放起了音樂,雖然年紀大了,但口味依舊還是許嵩,汪蘇瀧,薛之謙,毛不易等人的歌曲,倆字:好聽。
有時我就在想,每天重複著差不多的日子究竟有什麼意義,我會不會也變成那種25歲就死了,只是75歲才埋的人生,但,這不是我想要的人生。聽著汪蘇瀧翻唱的《我們》,我想到了一句很煽情很非主流的話:這麼些年我用泡沫堆砌起來的感情,雖然時常修繕小心呵護,可是風一吹,還是散了。
。。。。。。
來到公司,打卡,開電腦,簡單的吃個早飯,心想著今天做點什麼或寫點什麼,10月份已然過去了三分之一還多,可是自己還沒構思好要去寫點什麼,無聊的翻看著自己隨意寫的一些程式碼,麻木的點擊著公司項目上的功能,我看到了一棵樹,好吧,還真是一棵樹,會分叉還會橫著長的一棵樹,想了想,好久沒有專註於程式碼本身了,那就寫點實現某項功能的小例子,比如:使用遞歸結合Lambda表達式去實現這棵樹。在這裡,我想提供一個類似於思想模板的程式碼,基本使用它都能夠輕鬆完成後台的樹結構數據的實現,言歸正傳,開始吧。
二、遞歸和stream(),Lambda在十月相遇
1.問題來源
先看一張圖:
我們做的這塊是一個關於信票的功能,大體是一張信票我簽發給你,你再簽發給別人,別人再簽發給另一批人,另一批人也對外簽發,只要手持信票餘額足夠,是可以簽發給多人的,並且也是有可能A->B,B->C,C->A,至於具體的業務我就不做多描述(泄露不好不好),總得來說,我們需要用用一張圖來描繪出這種層級關係,明顯這是一種典型的樹狀結構(由於我們的每條數據有自己代號,所以剛剛我舉的ACBA並不會形成一個閉環,那兩個A所代表的層級也是不同的),這時候需要我們處理成樹狀架構的數據組給前台了。我展示的這張圖層級只到了2,而且還沒展示很複雜的結構(造條數據很麻煩的,原諒我的懶惰),但大體意思應該能看明白。
一看,有覺悟的後台同志們就明白了要用遞歸完成數據處理,我微微一笑,沒錯,肯定要用遞歸,然並軟,我的內心早已波濤洶湧:我的遞歸玩的是真不溜,工作到現在,還真沒正兒八經的用過遞歸思維。不過我不用太過擔心,畢竟最初始的這個任務不是落我頭上,不過這塊的業務我也參與了不少,所以我也得想想。同事去研究了,因為我們的這個數據不是直來直去的那種,並不是在表中存了父子id可以直接關聯取數據,所以處理起來也沒那麼容易,後來差不多同事忙活了不少時間,寫了一套程式碼,可能數據量一多的時候,貌似還會出問題,這就不是我關心的了。然而,後來我寫的一塊功能也用到了這個圖,這就關我事,參考了下網上處理這種問題的程式碼,我做了下處理和變形,完美契合,雖然程式碼多了點,但屢試不爽,下面是我處理後的程式碼:
package cn.exrick.xboot.modules.bill.entity; import com.alibaba.fastjson.JSON; import java.util.*; /** * zae */ public class BillNodeTree { public static Map getTreeList(List<BillCirculation> billCirculationList) { // 讀取層次數據結果集列表 List dataList = VirtualDataGenerator.getVirtualResult(billCirculationList); if(dataList==null || dataList.size()==0){ return new HashMap(); } // 節點列表(散列表,用於臨時存儲節點對象) HashMap nodeList = new HashMap(); // 根節點 Node root = null; // 根據結果集構造節點列表(存入散列表) for (Iterator it = dataList.iterator(); it.hasNext();) { Map dataRecord = (Map) it.next(); Node node = new Node(); node.id = (String)dataRecord.get("id"); node.orgIssueUnitUnnoName = (String) dataRecord.get("orgIssueUnitUnnoName"); node.level = Integer.parseInt(dataRecord.get("level")+""); node.parentId = (String)dataRecord.get("parentId"); nodeList.put(node.id, node); } // 構造無序的多叉樹 Set entrySet = nodeList.entrySet(); for (Iterator it = entrySet.iterator(); it.hasNext();) { Node node = (Node) ((Map.Entry) it.next()).getValue(); if (node.parentId == null || node.parentId.equals("") || "null".equals(node.parentId)) { root = node; } else { ((Node) nodeList.get(node.parentId)).addChild(node); } } // 輸出無序的樹形菜單的JSON字元串 // System.out.println(root.toString()); // 對多叉樹進行橫向排序 // root.sortChildren(); // 輸出有序的樹形菜單的JSON字元串 Map operatorMaps = (Map) JSON.parseObject(root.toString(),Map.class); return operatorMaps; } } /** * 節點類 */ class Node { /** * 節點編號 */ public String id; /** * 節點內容 */ public String orgIssueUnitUnnoName; /** * 級別 */ public Integer level; /** * 父節點編號 */ public String parentId; /** * 孩子節點列表 */ private Children children = new Children(); // 先序遍歷,拼接JSON字元串 public String toString() { String result = "{" + "id : '" + id + "'" + ", orgIssueUnitUnnoName : '" + orgIssueUnitUnnoName + "'" + ", level : " + level + ""; if (children != null && children.getSize() != 0) { result += ", children : " + children.toString(); } else { result += ", expand : false"; } return result + "}"; } // 兄弟節點橫向排序 public void sortChildren() { if (children != null && children.getSize() != 0) { children.sortChildren(); } } // 添加孩子節點 public void addChild(Node node) { this.children.addChild(node); } } /** * @author zangchuanlei * 孩子列表類 */ class Children { private List list = new ArrayList(); public int getSize() { return list.size(); } public void addChild(Node node) { list.add(node); } // 拼接孩子節點的JSON字元串 public String toString() { String result = "["; for (Iterator it = list.iterator(); it.hasNext();) { result += ((Node) it.next()).toString(); result += ","; } result = result.substring(0, result.length() - 1); result += "]"; return result; } // 孩子節點排序 public void sortChildren() { // 對本層節點進行排序 // 可根據不同的排序屬性,傳入不同的比較器,這裡傳入ID比較器 Collections.sort(list, new NodeIDComparator()); // 對每個節點的下一層節點進行排序 for (Iterator it = list.iterator(); it.hasNext();) { ((Node) it.next()).sortChildren(); } } } /** * @author zangchuanlei * 節點比較器 */ class NodeIDComparator implements Comparator { // 按照節點編號比較 public int compare(Object o1, Object o2) { int j1 = Integer.parseInt(((Node)o1).id); int j2 = Integer.parseInt(((Node)o2).id); return (j1 < j2 ? -1 : (j1 == j2 ? 0 : 1)); } } /** * @author zangchuanlei * 構造虛擬的層次數據 */ class VirtualDataGenerator { // 構造無序的結果集列表,實際應用中,該數據應該從資料庫中查詢獲得; public static List getVirtualResult(List<BillCirculation> billCirculationList) { List dataList = new ArrayList(); for(BillCirculation billCirculation:billCirculationList){ HashMap dataRecord1 = new HashMap(); dataRecord1.put("id", billCirculation.getId()); dataRecord1.put("orgIssueUnitUnnoName",billCirculation.getCorpName()); dataRecord1.put("level",billCirculation.getLevel()); if(billCirculation.getLevel() == 1){ dataRecord1.put("parentId", ""); }else{ String parentId = null; for(BillCirculation billCirculation1:billCirculationList){ if(billCirculation.getIssueUnitUnno().equals(billCirculation1.getSignInUnitUnno()) && billCirculation1.getLevel() == billCirculation.getLevel()-1){ parentId = billCirculation1.getId(); } } dataRecord1.put("parentId",parentId); } dataList.add(dataRecord1); } return dataList; } }
坦白說,雖然我貼出了這套自己改編後的程式碼,但是卻不是我今天講的重點,畢竟核心思想是人家的,我只是將它和自己的要寫的功能完美契合了,而且復用性不強,它用了些比較器進行處理,程式碼也太長太雜,和我今天的主題遞歸+Lambda不太符合,所以看看就行了,別太認真,重點在下面,這一塊的內容只是背景而已,並且,為了通俗的講重點,我會使用最最最簡單的例子讓大家看明白這個遞歸思維,所以如果覺得太基礎太陋可以消息立正向右轉不送。
2.重點內容
2.1創建實體Tree
import java.util.List; public class Tree { private Integer id;//id(唯一標識) private String number;//編號 private String name;//名稱 private Integer parentId;//父節點的ID代號 private Integer myId;//我的ID代號 //正式連接資料庫的項目中,此為添加的屬性,不是資料庫映射欄位 private List<Tree> children; public Tree() {} public Tree(Integer id, String number, String name, Integer parentId, Integer myId, List<Tree> children) { this.id = id; this.number = number; this.name = name; this.parentId = parentId; this.myId = myId; this.children = children; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getParentId() { return parentId; } public void setParentId(Integer parentId) { this.parentId = parentId; } public Integer getMyId() { return myId; } public void setMyId(Integer myId) { this.myId = myId; } public List<Tree> getChildren() { return children; } public void setChildren(List<Tree> children) { this.children = children; } }
2.2 核心程式碼
import com.alibaba.fastjson.JSON; import com.zaevn.entity.Tree; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class LambdaRecursion { public static void main(String[] args) { //獲取所有待處理的數據 List<Tree> treeList = getAllTree(); List<Tree> collect = treeList.stream() //篩選出所有的一級數據(父樹不存在) .filter(tree -> tree.getParentId() == 0) //為一級節點數據設置孩子(調用寫的獲取子樹的遞歸方法) .map(tree -> { tree.setChildren(getChrildren(tree, treeList)); return tree; }).collect(Collectors.toList()); //輸出一下處理好的數據 System.out.println(JSON.toJSONString(collect)); } /** * 遞歸獲取子樹 * @param root 父樹實體 * @param all 全部數據 * @return */ private static List<Tree> getChrildren(Tree root,List<Tree> all){ List<Tree> treeList = all.stream() //篩選出源節點的子節點 .filter(tree -> tree.getParentId() == root.getMyId()) //給當前節點設置子樹,調用自身獲取子樹的遞歸方法 .map(tree -> { tree.setChildren(getChrildren(tree, all)); return tree; }).collect(Collectors.toList()); return treeList; } /** * 數據準備類 * (實際開發中不存在此方法,應該在資料庫中獲取數據) * @return */ private static List<Tree> getAllTree(){ Tree one = new Tree(1001,"1001","皇帝",0,1,new ArrayList<>()); Tree two1 = new Tree(1002,"1002","耶穌",1,2,new ArrayList<>()); Tree two2 = new Tree(1003,"1003","鬼影",1,3,new ArrayList<>()); Tree three1 = new Tree(1004,"1004","軍師",2,4,new ArrayList<>()); Tree three2 = new Tree(1005,"1005","野獸",2,5,new ArrayList<>()); Tree three3 = new Tree(1006,"1006","伯爵",3,6,new ArrayList<>()); Tree three4 = new Tree(1007,"1007","牧師",3,7,new ArrayList<>()); //實際應用中需要在資料庫中獲取全部的數據 List<Tree> treeList = Arrays.asList(one,two1,two2,three1,three2,three3,three4); return treeList; } }
其實真正的核心程式碼只有那個main函數以及遞歸獲取子樹的靜態方法,是不是很簡短很簡單就能輕鬆的實現樹狀數據的處理,之所以程式碼可以縮減到那麼一丁點,主要得益於java stream的函數式編程結合lambda表達式的簡短程式碼,調用filter函數直接過濾出我們想要的數據,然後調用map函數對管道流中某個或某些元素進行處理,最後調用collect函數toList,將管道流轉換為List返回,當然,需要對返回的數據進行排序的話還可以使用sort函數進行排序。
大家注意要完成這種數據結構的處理時,首先需要將源節點(最高級父節點)的數據拿出來,也就是我們要的數據的最外層的數據,其次,想辦法往裡面填充子節點,這裡建議的是在實體中添加一個欄位,用來存放眾多的子節點數據,在獲取子節點時,就需要寫一個我們今天的主角遞歸方法了,通過設定過濾規則,找出共性,不斷調用自身,就可以完美的將孩子節點的數據裝入每一個節點中,直到再也找不到數據為之。
核心程式碼很短,也加了注釋,萬變不離其宗,大家遇到此類需要寫樹狀數據的任務時,別慌,基本參考下我這個小例子,就能很快的寫出來,也不用向我之前一樣,在網上找了一個這麼冗長的程式碼,雖然改造成功了,但是核心思想卻沒有透徹理解,還是得多練,通過這個小demo,是不是發現,其實遞歸也沒有那麼難。
好了,差點忘了運行下看下結果:
[{"children":[{"children":[{"children":[],"id":1004,"myId":4,"name":"軍師","number":"1004","parentId":2},{"children":[],"id":1005,"myId":5,"name":"野獸","number":"1005","parentId":2}],"id":1002,"myId":2,"name":"耶穌","number":"1002","parentId":1},{"children":[{"children":[],"id":1006,"myId":6,"name":"伯爵","number":"1006","parentId":3},{"children":[],"id":1007,"myId":7,"name":"牧師","number":"1007","parentId":3}],"id":1003,"myId":3,"name":"鬼影","number":"1003","parentId":1}],"id":1001,"myId":1,"name":"皇帝","number":"1001","parentId":0}]
(觀眾一臉黑線,這是什麼鬼!)我是在控制台上輸出的json串,所以我們可以登錄下//www.bejson.com/將這個json串放進去解析一下, 結果如下:
最後附上我嵩歌今日發行的新歌的歌詞結束(我嵩哥就是有才):
」晚秋 乘冷冷西風 施一葦渡江功
履尖 知江湖涼薄 於恍惚浮沉後
孤身獨心 快意領教紅塵三兩課
爾後勿書 勿見 勿擾我清夢
酒旗 飄揚在半坡 饞蟲趨之難鎖
落座 入一壺金波 浣往事穿腸過
鄰桌有客 形色疏狂 貂裘被胡霜
醉聞其口頻吐蠻言來犯我
你的放肆 燒燃起怒火
我的放肆 如大雨滂沱
冷刀喉前過 手沉疾如風
互以命相搏
你的放肆 掀風起雲湧
我的放肆 平滄海瀾波
皆謂我落拓 誰解我瘋魔
酒醒 竟是你 在關外候我」
獐死於麝 鹿死於角
危險和榮譽總是成正比噠
請大家多多批評指教哈