重构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
。 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); } }