背景
在項目中,使用到了將菜單數(shù)據(jù)轉(zhuǎn)換為數(shù)格式返回給前端的需求硝岗。從數(shù)據(jù)庫中讀取的數(shù)據(jù)莉掂,是以list格式讀取的,那么如何使用java代碼將list轉(zhuǎn)換為tree 呢默垄?
tree型數(shù)據(jù)結(jié)構(gòu)此虑,類似組合模式,只不過組合的子項是自身
定義TreeNode 節(jié)點
定義TreeNode節(jié)點數(shù)厕倍,節(jié)點中提供了addChildren/addChild
等方法管理子節(jié)點
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class TreeNode {
private static final long serialVersionUID = 1L;
private String id;
private String parentId;
private String title;
private String key;
private String value;
private Collection<TreeNode> children = new ArrayList<>();
public void addChild(TreeNode treenode) {
this.initChildren();
this.children.add(treenode);
}
public void addChildren(List<TreeNode> children) {
this.initChildren();
Optional.ofNullable(children)
.ifPresent(c -> this.children.addAll(c));
}
public void clearChildren() {
this.initChildren();
this.children.clear();
}
private void initChildren() {
if (this.children == null) {
this.children = new ArrayList<>();
}
}
}
使用遞歸實現(xiàn)
遞歸是循環(huán)list里的數(shù)據(jù)寡壮,從list中讀取該項的所有子項
public static List<TreeNode> listToTree(List<TreeNode> items) {
// 查出父節(jié)點
List<TreeNode> rootList = items.stream().filter(c->c.getParentId()==null||c.getParentId()==0L).collect(Collectors.toList());
// 按 parentid - list 進行分組
Map<Long, List<TreeNode>> collect =
items.stream().collect(Collectors.groupingBy(ITreeNode::getParentId));
toTree(collect,rootList);
return rootList;
}
private static void toTree(Map<Long, List<TreeNode>> collect, List<TreeNode> rootNodeList){
// 根據(jù)分組情況設(shè)置子節(jié)點
if(rootNodeList==null || rootNodeList.isEmpty()){
return ;
}
rootNodeList.forEach(c->{
// 查找子節(jié)點
List<TreeNode> children = collect.get(c.getId());
c.addChildren(children);
// 遞歸調(diào)用
toTree(collect,children);
});
}
非遞歸,使用棧
所有遞歸調(diào)用的方法讹弯,都可以使用棧完成
/**
* 使用棧替換遞歸<br><br/>
* 在棧中存儲每一次遞歸方法調(diào)用的參數(shù)<br><br/>
* 參考快速排序非遞歸實現(xiàn)
*
* @param items list 數(shù)組
* @param <T> 樹節(jié)點
* @return 樹形列表
*/
public static List<TreeNode> quickListToTree(List<TreeNode> items) {
List<T> rootList = items.stream().filter(c -> c.getParentId() == null || c.getParentId().equals(0L)).collect(Collectors.toList());
// 用一個棧集合代替遞歸的函數(shù)棧
Stack<List<T>> quickStack = new Stack<>();
// 整個列表的跟况既,已list形式入棧
quickStack.push(rootList);
// 循環(huán)結(jié)束條件,棧為空時
while (!quickStack.isEmpty()) {
// 棧頂元素出棧,得到父級元素列表
List<TreeNode> root = quickStack.pop();
if (CollectionUtils.isEmpty(root)) {
break;
}
// 根據(jù)父級元素组民,查找所有的下一子級元素信息
List<TreeNode> childList = items.stream().filter(c -> root.stream().anyMatch(r -> r.getId().equals(c.getParentId())))
.collect(Collectors.toList());
// 填充父級的子級元素
root.forEach(r -> r.addChildren(childList.stream().filter(c -> c.getParentId().equals(r.getId())).collect(Collectors.toList())));
// 如果下一自己元素為空棒仍,則進行下一輪
if (!CollectionUtils.isEmpty(childList)) {
quickStack.push(childList);
}
}
return rootList;
}
組合模式的意圖
將對象組合成樹結(jié)構(gòu)以表示部分-整體層次結(jié)構(gòu)。 Composite 允許客戶端統(tǒng)一處理單個對象和對象的組合臭胜。
復(fù)合模式讓客戶端以統(tǒng)一的方式處理各個對象莫其。
背景案例
每個句子都由單詞組成,而單詞又由字符組成耸三。這些對象中的每一個都是可打印的乱陡,它們可以在它們之前或之后打印一些東西,例如句子總是以句號結(jié)尾仪壮,單詞之前總是有空格
在軟件中憨颠,復(fù)合模式是一種設(shè)計模式。復(fù)合模式描述了一組對象的處理方式與對象的單個實例相同。
復(fù)合的目的是將對象“組合”成樹結(jié)構(gòu)以表示部分-整體層次結(jié)構(gòu)爽彤。實現(xiàn)復(fù)合模式可以讓客戶統(tǒng)一對待單個對象和組合
代碼案例
以上面的句子為例养盗。這里我們有基類 LetterComposite
和不同的可打印類型 Letter、Word适篙、Sentence
往核。
public abstract class LetterComposite {
private final List<LetterComposite> children = new ArrayList<>();
public void add(LetterComposite letter) {
children.add(letter);
}
public int count() {
return children.size();
}
protected void printThisBefore() {
}
protected void printThisAfter() {
}
public void print() {
printThisBefore();
children.forEach(LetterComposite::print);
printThisAfter();
}
}
public class Letter extends LetterComposite {
private final char character;
public Letter(char c) {
this.character = c;
}
@Override
protected void printThisBefore() {
System.out.print(character);
}
}
public class Word extends LetterComposite {
public Word(List<Letter> letters) {
letters.forEach(this::add);
}
public Word(char... letters) {
for (char letter : letters) {
this.add(new Letter(letter));
}
}
@Override
protected void printThisBefore() {
System.out.print(" ");
}
}
public class Sentence extends LetterComposite {
public Sentence(List<Word> words) {
words.forEach(this::add);
}
@Override
protected void printThisAfter() {
System.out.print(".");
}
}
組裝
public class Messenger {
LetterComposite messageFromOrcs() {
var words = List.of(
new Word('W', 'h', 'e', 'r', 'e'),
new Word('t', 'h', 'e', 'r', 'e'),
new Word('i', 's'),
new Word('a'),
new Word('w', 'h', 'i', 'p'),
new Word('t', 'h', 'e', 'r', 'e'),
new Word('i', 's'),
new Word('a'),
new Word('w', 'a', 'y')
);
return new Sentence(words);
}
LetterComposite messageFromElves() {
var words = List.of(
new Word('M', 'u', 'c', 'h'),
new Word('w', 'i', 'n', 'd'),
new Word('p', 'o', 'u', 'r', 's'),
new Word('f', 'r', 'o', 'm'),
new Word('y', 'o', 'u', 'r'),
new Word('m', 'o', 'u', 't', 'h')
);
return new Sentence(words);
}
}
使用
var orcMessage = new Messenger().messageFromOrcs();
orcMessage.print(); // Where there is a whip there is a way.
var elfMessage = new Messenger().messageFromElves();
elfMessage.print(); // Much wind pours from your mouth.
何時使用設(shè)計模式
- 想要表示對象的部分-整體層次結(jié)構(gòu)
- 希望客戶端能夠忽略對象組合和單個對象之間的差異∪陆冢客戶端將統(tǒng)一處理復(fù)合結(jié)構(gòu)中的所有對象聂儒。