前言:
相信大家都打开过层级很多很多的文件夹. 如果把第一个文件夹看作是树的根节点的话, 下面的子文件夹就可以看作一个子节点. 不过最终我们寻找的还是文件夹中的文件, 文件可以看做是叶子节点. 下面我们介绍一种模式, 与这种树级结构息息相关. 当然, 今天的主角是 HashMap. 接下来我们一起来看 HashMap 中到底是怎么跟树级结构进行挂钩的.
一, 定义
将对象组合成树形结构以表示 "部分 - 整体" 的一个层次结构, 使客户端对单个对象和组合对象保持一致的方式处理.
二, 适用场景
1, 客户端可以忽略组合对象与单个对象的差异
注意组合模式中的概念, 当客户端使用组合模式的时候是对单个对象和组合对象保持一致的方式处理, 而不是不同的方式处理. 所以如果客户端可以忽略组合对象和单个对象的差异时, 才用组合模式.
2, 处理一个树形结构
这里就很好理解了, 组合模式就是处理树形结构的
三, 结合 HashMap 看组合模式
1, 抽象构件: 总的抽象类或者接口, 定义一些通用的方法, 比如新增, 删除.
2, 中间构件: 继承或者实现抽象构件, 定义存储方式, 并针对特殊需要重写抽象构件的方法.
3, 叶子节点: 继承或者实现抽象构件, 并针对特殊需要重写抽象构件的方法.
叶子节点需要实现或者继承抽象构件, 如果有需要也可以添加到中间构件上. 就比如说我现在进入 D 盘, D 盘就是一个抽象构件, 无论在 D 盘哪里, 我都可以执行新建, 删除操作. 中间构件就相当于文件夹, 里面定义了相应的存储方式, 加入的文件是叶子节点, 并且会按照这种方式来存储, 同时这个中间构件也可以选择再加入一个其他中间构件或者自己. 叶子节点就可以看作文件. 注意, 在组合模式下所有类都需要直接或者间接, 继承或实现总的抽象构件. 下面讲 HashMap 中的 putAll()方法
- public class HashMap<K,V> extends AbstractMap<K,V>
- implements Map<K,V>, Cloneable, Serializable {
- public void putAll(Map<? extends K, ? extends V> m) {
- putMapEntries(m, true);
- }
- final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
- int s = m.size();
- if (s> 0) {
- if (table == null) { // pre-size
- float ft = ((float)s / loadFactor) + 1.0F;
- int t = ((ft <(float)MAXIMUM_CAPACITY) ?
- (int)ft : MAXIMUM_CAPACITY);
- if (t> threshold)
- threshold = tableSizeFor(t);
- }
- else if (s> threshold)
- resize();
- for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
- K key = e.getKey();
- V value = e.getValue();
- putVal(hash(key), key, value, false, evict);
- }
- }
- }
- }
上面是简化的 HashMap. 我们看见 putAll 方法传入的是 Map 对象, Map 就是一个抽象构件(同时这个构件中只支持键值对的存储格式), 而 HashMap 是一个中间构件, HashMap 中的 Node 节点就是叶子节点. 说到中间构件就会有规定的存储方式. HashMap 中的存储方式是一个静态内部类的数组 Node<K,V>[] tab. 下面是简化的具体代码
- public class HashMap<K,V> extends AbstractMap<K,V>
- implements Map<K,V>, Cloneable, Serializable {
- static class Node<K,V> implements Map.Entry<K,V> {
- final int hash;
- final K key;
- V value;
- Node<K,V> next;
- Node(int hash, K key, V value, Node<K,V> next) {
- this.hash = hash;
- this.key = key;
- this.value = value;
- this.next = next;
- }
- final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
- boolean evict) {
- Node<K,V>[] tab; Node<K,V> p; int n, i;
- if ((tab = table) == null || (n = tab.length) == 0)
- n = (tab = resize()).length;
- if ((p = tab[i = (n - 1) & hash]) == null)
- tab[i] = newNode(hash, key, value, null);
- else {
- Node<K,V> e; K k;
- if (p.hash == hash &&
- ((k = p.key) == key || (key != null && key.equals(k))))
- e = p;
- else if (p instanceof TreeNode)
- e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
- else {
- for (int binCount = 0; ; ++binCount) {
- if ((e = p.next) == null) {
- p.next = newNode(hash, key, value, null);
- if (binCount>= TREEIFY_THRESHOLD - 1) // -1 for 1st
- treeifyBin(tab, hash);
- break;
- }
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- break;
- p = e;
- }
- }
- if (e != null) { // existing mapping for key
- V oldValue = e.value;
- if (!onlyIfAbsent || oldValue == null)
- e.value = value;
- afterNodeAccess(e);
- return oldValue;
- }
- }
- ++modCount;
- if (++size> threshold)
- resize();
- afterNodeInsertion(evict);
- return null;
- }
- }
所以我们调用 put 方法实际上是加入一个叶子节点, 而我们调用 putAll 相当于把其他 Map 下面的文件夹中的文件拷贝过来. 还有 Hashtable 也可以看作一个中间构件, 里面的 putAll 方法同样是适合放入实现 Map 接口的对象. 这里如果将 Hashtable 的这个中间构件放入 HashMap 中, 那么我们这些方法还能用吗. 答案是能用, 组合模式处理的就是一致性的问题. Map 接口中的方法, 比如 put(),remove(), 到下面的中间构件中其实都是一致的功能, 不过就是不同的中间构件中的处理方式可能会有细微的差别. 下面是我的测试代码
- public class Test {
- public static void main(String[] args) {
- Map<Integer,String> rootmap=new HashMap<Integer,String>();
- rootmap.put(1,"HashMap 文件 1");
- Map<Integer,String> map1=new Hashtable<Integer,String>();
- map1.put(2,"Hashtable 文件 1");
- map1.put(3,"Hashtable 文件 2");
- map1.put(4,"Hashtable 文件 3");
- rootmap.putAll(map1);
- System.out.println(rootmap);
- } }
输出结果为:
上面实现将 Hashtable 中的键值对放入 HashMap 中. 如果放在组合模式下, 你就可以看作是将 Hashtable 这个中间构件下的文件拷贝到 HashMap 这个中间构件中. 在我们电脑里文件任何类型的文件都可以拷到其他任意地方. 但是用来理解 Map 这个抽象构件的就不行, 因为实现 Map 接口的类只支持放入键值对格式的叶子节点, 如果不是 (比如说 ArrayList) 就不行, 这是底层方法不支持. 同时我们回到组合模式上重要的一点: 一致性, Map 下面添加都是 put 方法, 需要传入两个值, List 下都是 add 方法, 只需要传入一个值, 如果 List 中的可以通过调用 putAll 方法放入 Map 里, 那么我该怎么给 List 赋 key 或 value 呢.(不是说不能有 put(1,list)这样的操作, 而是说在组合模式下操作时候需要有一致性, 并且这里说的组合模式针对的是 putAll 方法, 与其他方法无关)
四, 总结
这里只是解析了 HashMap 中的 putAll 组合模式. 在平常写代码的过程中要使用组合模式, 就需要先定义一个抽象类或者接口. 在这里就可以看作是抽象构件, 主要实现一些基本的通用的功能. 接着如果有需要就要定义一个中间构件, 并且实现抽象构件, 里面的方法可以根据特殊需要重写. 而且这个类最重要的是要有存储结构在里面, 用 Map 存储, 或者用 List 存储等等都可以. 接着就是我们真正的叶子节点, 同样是继承或实现抽象构件, 里面的方法可以根据特殊需要重写. 当你看完这篇博客, 你可以再去看定义和适用场景, 说不定收获会更大.
来源: http://www.bubuko.com/infodetail-3016366.html