重构Map地图的Java Map

我正在审查一个项目的旧代码,并使用Map Map of Map (3-Layered Map)得到一个数据结构:

 // data structure Map<String, Map<String, Map<String, List>>> tagTree = new HashMap<String, Map<String,Map<String,List>>>(); 

并从Map中获取值(我认为这是很好的部分)

 // fetch at tag values List tagList1 = tagTree.get("Java").get("Active").get("Tags"); List tagList2 = tagTree.get("Java").get("Latest").get("SubTags"); 

将值放在Map中(有点复杂且容易出错)

 // put values Map<String, Map<String, List>> javaLangMap = new HashMap<String, Map<String, List>>(); Map<String, List> javaStatusMap = new HashMap<String, List>(); List javaTagList = new ArrayList(); javaTagList.add("Java-OOP"); javaTagList.add("Java-Variables"); // put tag list javaStatusMap.put("Tags", javaTagList); // put status-wise tag javaLangMap.put("Active", javaStatusMap); // put language-wise tag tagTree.put("Java", javaLangMap); 

目前,这有助于维持以下结构

TagLanguage – > TagStatus – > TagType – > TagList

我打算重构这张地图,因为很难为其他开发者阅读。

请分享您的想法如何通过考虑以下情况来做到这一点:

  • 在运行时期间可以更改所有四个层。
  • 所有级别都应该可访问
  • 需要内存中的解决方案,即不要使用数据库表层次结构。

如果您只想访问数据结构的最后一级,可以使用Multimap,String>Multimap是来自Guava的数据结构,它基本上是更好的Map>Triple是来自Apache Commons Lang3的3元素元组数据结构,它是Comparable并实现equals

您可以像这样声明标记树:

 Multimap, String> tagTree = HashMultimap.create(); 

然后像这样填写:

 tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-OOP"); tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-Variables"); 

要么:

 tagTree.putAll(Triple.of("Java", "Active", "Tags"), Arrays.asList("Java-OOP", "Java-Variables")); 

然后从中获取您的值,如下所示:

 Set values = tagTree.get(Triple.of("Java", "Active", "Tags")); 

这是另一个可能适合您的粗略解决方案,可以使用1,2或3个键:

 import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; public class ThreeLevelMap { private Map>> firstLevelMap = new HashMap<>(); private Map, Multimap> secondLevelMap = new HashMap<>(); private Multimap, V> thirdLevelMap = HashMultimap.create(); public void put(K1 key1, K2 key2, K3 key3, V value) { thirdLevelMap.put(Triple.of(key1, key2, key3), value); final Pair secondLevelKey = Pair.of(key1, key2); Multimap secondLevelContainer = secondLevelMap.get(secondLevelKey); if (secondLevelContainer == null) { secondLevelContainer = HashMultimap.create(); secondLevelMap.put(secondLevelKey, secondLevelContainer); } secondLevelContainer.put(key3, value); Map> firstLevelContainer = firstLevelMap.get(key1); if (firstLevelContainer == null) { firstLevelContainer = new HashMap<>(); firstLevelMap.put(key1, firstLevelContainer); } firstLevelContainer.put(key2, secondLevelContainer); } public Collection get(K1 key1, K2 key2, K3 key3) { return thirdLevelMap.get(Triple.of(key1, key2, key3)); } public Multimap get(K1 key1, K2 key2) { return secondLevelMap.get(Pair.of(key1, key2)); } public Map> get(K1 key1) { return firstLevelMap.get(key1); } } 

你可以这样使用它:

 ThreeLevelMap tlm = new ThreeLevelMap<>(); tlm.put("Java", "Active", "Tags", "Java-OOP"); tlm.put("Java", "Active", "Tags", "Java-Variables"); Map> firstLevelMap = tlm.get("Java"); Multimap secondLevelMap = tlm.get("Java", "Active"); Collection tags = tlm.get("Java", "Active", "Tags"); 

我说它很粗糙,因为:

  • get方法返回的映射是可修改的
  • 我没有实现remove方法
  • 我没有经常测试它

我不认为这是一个如此糟糕的解决方案。 它只是一个树表示,对于一棵树,每个叶子都在3级。如果不是这种情况(不同的叶子级别等),你将不得不建立一个树类结构。

但我要改变的是将所有内容放在一个类中 ,使用get和set方法,包括空值检查

在下面的代码中, add方法负责处理中间级别映射的容易出错的处理,同时在中间级别检查空值:

 public class TreeStructure { Map>>> tagTree = new HashMap>>>(); // ... Constructor ... // This method adds all intermediate levels if not existing public void add(String level1, String level2, String level3) { String l1 = tagTree.get(level1); if(l1 == null) tagTree.put(level1, new HashMap>>()); l1 = tagTree.get(level1); String l2 = l1.get(level2), if(l2 == null) tagTree.put(level2, new Map>();); l2 = l1.get(level2); String l3 = l2.get(level3); if(l3 == null) l2.add(level3, new ArrayList<>()); } // This method checks, if every intermediate level existed // Otherwise, get() returns null, and the next get() would fail public String get(String level1, String level2, String level3) { String l1 = tagTree.get(level1); if(l1 == null) return null; String l2 = l1.get(level2), if(l2 == null) return null; l2 = l1.get(level2); String l3 = l2.get(level3); return l3; } } 

(代码未经测试)

您可以创建将保存数据结构的类

 public class A { Map> map; } public class B { Map map; } Map tagTree; 

使用3元组的地图:

 class Tuple3 { private A a; private B b; private C c; // getters, setters, constructor // make sure equals() and hashCode() are okay } 

但请注意,地图的地图可以通过查看外部地图在O(1)中告诉您是否有某些元素的条目。 然而,使用元组解决方案,您只能使用完整密钥。

我认为没有理由重构这个Map结构。 但是如果可能的话,将Map封装在另一个class并为其他开发人员提供一个干净的界面可能是个好主意。

 ... public void addTag(String language, String status, String tag) public void removeTag(String language, String status, String tag) public List getTags(String language, String status) ... 

我喜欢上面提到的大多数解决方案。 我认为设计更简单有效 – 它将更加可支持。

因此,我使用基础 – 普通对象组合来重构代码。

 package design; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; class JavaTag{ private String tags; JavaTag(String tags){ this.tags = tags; } } class JavaTagStatusList{ private ArrayList tagList = new ArrayList(); JavaTagStatusList(){ } public void addJavaTag (JavaTag tagObj){ if (tagObj != null){ tagList.add(tagObj); } } } class JavaTagStatusMap { private HashMap tagStatusMap = new HashMap(); JavaTagStatusMap(){ } public void addTagStatusEntry(String status, JavaTag obj){ if (tagStatusMap.containsKey(status)){ tagStatusMap.get(status).addJavaTag(obj); } else { JavaTagStatusList statusList = new JavaTagStatusList(); statusList.addJavaTag(obj); tagStatusMap.put(status, statusList); } } } 

主要:

 public class MapofMapRefactor { public static void main(String[] args) { JavaTag tag1 = new JavaTag("Java-OOP"); JavaTag tag2 = new JavaTag("Java-Variables"); JavaTagStatusMap statusMap = new JavaTagStatusMap(); statusMap.addTagStatusEntry("Active", tag1); statusMap.addTagStatusEntry("Active", tag2); // HashMap of Java Lang Map HashMap javaLanguageMap = new HashMap(); javaLanguageMap.put("Java", statusMap); } }