JDK8源码阅读笔记
切换暗/亮/自动模式 切换暗/亮/自动模式 切换暗/亮/自动模式 返回首页

AbstractMap



此类提供了Map接口的框架实现,以最大程度减少实现此接口所需的工作量。

要实现一个不可修改的map,编程者只需扩展这个类,并为entrySet方法提供一个实现,该方法返回map映射的set视图。通常,返回的set将基于AbstractSet实现。此set不应支持addremove方法,其迭代器不应支持remove方法。

要实现一个可修改的map,编程者必须额外重写这个类的put方法(否则会抛出一个UnsupportedOperationException),并且由entrySet().iterator()返回的迭代器必须额外实现它的remove方法。

根据Map接口规范中的建议,编程者通常应提供一个void(无参数)构造函数和一个参数为map的构造函数。

此类中每个非抽象方法的文档都详细描述了其实现。如果正在实现的map允许更有效的实现,则这些方法中的每一个都可能被重写。

本类是Java集合框架的成员。


AbstractMap


1. protected AbstractMap()

唯一的构造器。(子类构造函数调用,通常是隐式的。)

protected AbstractMap() {
}

2. public int size()

返回当前map的元素个数,本实现是返回的entrySet().size()

public int size() {
    return entrySet().size();
}

3. public boolean isEmpty()

判断当前map是否为空。

public boolean isEmpty() {
    return size() == 0;
}

4. public boolean containsValue(Object value)

本实现遍历entrySet()以找到一个值为参数所指定的value的条目。如果找到了这样的条目,则返回true,如果遍历完也没有找到这样的条目,则返回false。请注意,此实现需要map大小的线性时间。

public boolean containsValue(Object value) {
    // 先调用entrySet()获取条目的Set,然后再获取该Set的迭代器
    Iterator<Entry<K,V>> i = entrySet().iterator();
    // 在遍历集合的外面先判断出value是否为空,避免每迭代到一个条目都进行一次判断
    if (value==null) {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (e.getValue()==null)
                return true;
        }
    } else {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (value.equals(e.getValue()))
                return true;
        }
    }
    return false;
}

5. public boolean containsKey(Object key)

本实现遍历entrySet()以找到一个键为参数所指定的key的条目。如果找到了这样的条目,则返回true,如果遍历完也没有找到这样的条目,则返回false。请注意,此实现需要map大小的线性时间,许多实现将重写此方法。

源码如下:

public boolean containsKey(Object key) {
    // 获取条目的集合的迭代器
    Iterator<Map.Entry<K,V>> i = entrySet().iterator();
    // 区分指定的key是否为空,然后遍历集合找出指定键的条目
    if (key==null) {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (e.getKey()==null)
                return true;
        }
    } else {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (key.equals(e.getKey()))
                return true;
        }
    }
    return false;
}

6. public V get(Object key)

本实现遍历entrySet()以找到一个键为参数所指定的key的条目。如果找到了这样的条目,则返回该条目的值,如果遍历完也没有找到这样的条目,则返回null。请注意,此实现需要map大小的线性时间,许多实现将重写此方法。

源码如下:

public V get(Object key) {
    // 该方法与containsKey类似,区别在于找到对应条目后,containsKey是返回true,而get是返回条码的值
    Iterator<Entry<K,V>> i = entrySet().iterator();
    if (key==null) {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (e.getKey()==null)
                return e.getValue();
        }
    } else {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (key.equals(e.getKey()))
                return e.getValue();
        }
    }
    return null;
}

7. public V put(K key, V value)

向当前map中添加(或更新)键值对。此默认实现中是抛出UnsupportedOperationException,所以如果想实现可修改的Map需要重写该方法。

源码如下:

public V put(K key, V value) {
    throw new UnsupportedOperationException();
}

8. public V remove(Object key)

此实现遍历ntrySet()以找到一个键为参数所指定的key的条目。如果找到这样一个条目,则使用其getValue操作获取其值,使用迭代器的remove操作从集合(和后备map)中删除该条目,并返回刚取出的值。如果遍历终止时没有找到这样的条目,则返回null。请注意,此实现需要map大小的线性时间,许多实现将覆盖此方法。

注意,如果entrySet的迭代器不支持remove方法并且当前map包含指定键的映射,则本实现会抛出UnsupportedOperationException

源码如下:

public V remove(Object key) {
    // 获取到entrySet的迭代器
    Iterator<Entry<K,V>> i = entrySet().iterator();
    // 定义一个变量用于指向要寻找的条目(即键为参数中指定的key)
    Entry<K,V> correctEntry = null;
    // 根据指定键是否为空来确认是采用“==null”判断还是采用“equals”判断
    if (key==null) {
        // 如果要寻找的条目为空则继续遍历集合
        while (correctEntry==null && i.hasNext()) {
            Entry<K,V> e = i.next();
            // 如果符合条件,则将当前迭代到的条目赋值给变量correctEntry
            if (e.getKey()==null)
                correctEntry = e;
        }
    } else {
        while (correctEntry==null && i.hasNext()) {
            Entry<K,V> e = i.next();
            if (key.equals(e.getKey()))
                correctEntry = e;
        }
    }

    // 定义一个变量oldValue用于指向旧值
    V oldValue = null;
    // 如果correctEntry不为空,说明找到了对应的条目
    if (correctEntry !=null) {
        // 取出该条目的值赋值给oldValue
        oldValue = correctEntry.getValue();
        // 然后使用迭代器的remove方法删除此条目
        i.remove();
    }
    // 返回旧值,当然如果当前map不包含指定键,则返回null
    return oldValue;
}

9. public void putAll(Map<? extends K, ? extends V> m)

此实现遍历指定map的entrySet()集合,并为遍历到的每个条目调用一次本map的put操作。

请注意,如果此映射不支持put操作且指定的map非空,则此实现将抛出一个UnsupportedOperationException

源码如下:

public void putAll(Map<? extends K, ? extends V> m) {
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        put(e.getKey(), e.getValue());
}

10. public void clear()

本实现调用entrySet().clear()以删除当前map内的所有映射。

注意,如果entrySet不支持clear操作,则本实现将抛出UnsupportedOperationException

public void clear() {
    entrySet().clear();
}

11. transient Set<K> keySet;

在第一次请求此视图时,这些字段中的每个字段都会初始化为包含相应视图的实例。视图是无状态的,因此没有理由创建多个视图。

由于在访问这些字段时没有执行同步,因此使用这些字段的java.util.Map视图类应该没有非final的字段(或除此之外的任何字段)。遵守这一规则将使这些领域的竞赛变得良性。

实现也必须只读取一次字段,如:

public Set<K> keySet() {
    Set<K> ks = keySet;  // single racy read
    if (ks == null) {
      ks = new KeySet();
      keySet = ks;
    }
    return ks;
}

12. transient Collection<V> values;

在第一次请求此视图时,这些字段中的每个字段都会初始化为包含相应视图的实例。视图是无状态的,因此没有理由创建多个视图。


13. public Set<K> keySet()

此实现返回一个AbstractSet的子类的集。子类的迭代器方法在该映射的entrySet()迭代器上返回一个“包装器对象”。size方法委托给此map的size方法,contains方法委托给此map的containsKey方法。

在第一次调用此方法时创建该集合,并在响应所有后续调用时返回该集合。因为没有执行同步,因此对该方法的多个调用很可能不会全部返回同一个集合。

源码如下:

public Set<K> keySet() {
    Set<K> ks = keySet;
    // 先看类变量keySet是否为null
    if (ks == null) {
        // 若为空,则创建一个AbstractSet的子类的实例
        ks = new AbstractSet<K>() {
            // 迭代器方法实现
            public Iterator<K> iterator() {
                return new Iterator<K>() {
                    // 获取entrySet()方法所返回集合的迭代器,以下三个方法均基于该迭代器
                    private Iterator<Entry<K,V>> i = entrySet().iterator();

                    public boolean hasNext() {
                        return i.hasNext();
                    }

                    public K next() {
                        return i.next().getKey();
                    }

                    public void remove() {
                        i.remove();
                    }
                };
            }

            // 以下四个方法均基于AbstractMap的对应方法
            public int size() {
                return AbstractMap.this.size();
            }

            public boolean isEmpty() {
                return AbstractMap.this.isEmpty();
            }

            public void clear() {
                AbstractMap.this.clear();
            }

            public boolean contains(Object k) {
                return AbstractMap.this.containsKey(k);
            }
        };
        // 将刚创建的实例赋值给类变量keySet
        keySet = ks;
    }
    // 若不为空,直接返回
    return ks;
}

14. public Collection<V> values()

此实现返回一个AbstractCollection的子类的集合。子类的迭代器方法在该映射的entrySet()迭代器上返回一个“包装器对象”。size方法委托给此map的size方法,contains方法委托给此map的containsKey方法。

在第一次调用此方法时创建该集合,并在响应所有后续调用时返回该集合。因为没有执行同步,因此对该方法的多个调用很可能不会全部返回同一个集合。

源码如下:

// 该方法在实现思路上与public Set<K> keySet()方法完全一致
public Collection<V> values() {
    Collection<V> vals = values;
    if (vals == null) {
        vals = new AbstractCollection<V>() {
            public Iterator<V> iterator() {
                return new Iterator<V>() {
                    private Iterator<Entry<K,V>> i = entrySet().iterator();

                    public boolean hasNext() {
                        return i.hasNext();
                    }

                    public V next() {
                        return i.next().getValue();
                    }

                    public void remove() {
                        i.remove();
                    }
                };
            }

            public int size() {
                return AbstractMap.this.size();
            }

            public boolean isEmpty() {
                return AbstractMap.this.isEmpty();
            }

            public void clear() {
                AbstractMap.this.clear();
            }

            public boolean contains(Object v) {
                return AbstractMap.this.containsValue(v);
            }
        };
        values = vals;
    }
    return vals;
}

15. public abstract Set<Entry<K,V» entrySet();

抽象方法,获取当前map的条目的集合。


16. public boolean equals(Object o)

比较指定对象是否与当前map相等。如果给定的对象也是一个map并且这两个map表示相同的映射,则返回true。更正式地说,如果两个mapm1m2满足m1.entrySet().equals(m2.entrySet()),则m1m2表示相同的映射。这确保equals方法在Map接口的不同实现中正常工作。

这个实现首先检查指定的对象是否是本映射,如果是,则返回true。然后,检查指定对象是否是大小与此map的大小相同,如果不是,则返回false,如果是这样,它将迭代此map的entrySet集合,并检查指定的map是否包含此map包含的每个映射。如果指定的map未能包含这样的映射,则返回false。如果迭代完成,则返回true

源码如下:

public boolean equals(Object o) {
    // 若指定对象就是当前map,则直接返回true
    if (o == this)
        return true;

    // 若指定对象不是Map,则直接返回false
    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    // 若指定对象是map,但元素数量与当前map不一致,则直接返回false
    if (m.size() != size())
        return false;

    try {
        // 迭代所有键值对
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            // 取值当前映射的键和值
            K key = e.getKey();
            V value = e.getValue();
            // 按照值是否为空采取不同的判断方式
            if (value == null) {
                // 当前map的当前映射的值为空,则参数中指定的map的当前值也应该为空包含当前key,否则一票否决,直接返回false
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    // 迭代完,所有校验都通过了,返回true
    return true;
}

17. public int hashCode()

返回此map的哈希码值。map的哈希码定义为map的entrySet()视图中每个条目的哈希码之和。这确保对于任何两个map m1m2来说m1.equals(m2)意味着m1.hashCode()==m2.hashCode(),这也是ObjecthashCode通用规范要求。

此实现迭代entrySet(),对集合中的每个元素(条目)调用Map.Entry.hashCode(),并将结果相加。

源码如下:

public int hashCode() {
    int h = 0;
    // 遍历所有条目,将每个条目的hash编码值加起来得到本map的hash编码值
    Iterator<Entry<K,V>> i = entrySet().iterator();
    while (i.hasNext())
        h += i.next().hashCode();
    return h;
}

18. public String toString()

返回此map的字符串表示形式。字符串表示法由键值映射列表组成,其顺序为map的entrySet视图的迭代器返回,并用大括号"{}"括起来。相邻映射由字符", "(逗号和空格)分隔。每个键值映射都呈现为键后跟一个等号("="),后跟对应的值。键和值通过StringvalueOf(Object)转换为字符串。

源码如下:

public String toString() {
    // 先获取entrySet的迭代器
    Iterator<Entry<K,V>> i = entrySet().iterator();
    // 如果没有键值映射,则直接返回{}
    if (! i.hasNext())
        return "{}";

    // 使用StringBuilder来拼接字符串
    StringBuilder sb = new StringBuilder();
    // 先拼一个开头
    sb.append('{');
    // 遍历条目集合
    for (;;) {
        Entry<K,V> e = i.next();
        K key = e.getKey();
        V value = e.getValue();
        // 如果key是当前map本身,则使用"(this Map)"来表示
        sb.append(key   == this ? "(this Map)" : key);
        // key与value之间的连接符
        sb.append('=');
        // 如果value是当前map本身,则使用"(this Map)"来表示
        sb.append(value == this ? "(this Map)" : value);
        if (! i.hasNext())
            // 如果已迭代结束,则拼接一个结尾,然后返回字符串
            return sb.append('}').toString();
        // 未迭代结束,则拼接分隔符,此处不直接"sb.append(', ')"而是"sb.append(',').append(' ')"是出于什么考虑呢?
        sb.append(',').append(' ');
    }
}

19. protected Object clone() throws CloneNotSupportedException

返回此AbstractMap实例的浅层副本:键和值本身不会被克隆。

源码如下:

protected Object clone() throws CloneNotSupportedException {
    // 调用Object类的clone方法获取一个AbstractMap实例
    AbstractMap<?,?> result = (AbstractMap<?,?>)super.clone();
    // 将keySet和values均置空
    result.keySet = null;
    result.values = null;
    return result;
}

20. private static boolean eq(Object o1, Object o2)

SimpleEntrySimpleImmutableEntry的实用方法。测试是否相等,检查是否为空。

源码如下:

private static boolean eq(Object o1, Object o2) {
    return o1 == null ? o2 == null : o1.equals(o2);
}

21. public static class SimpleEntry<K,V> implements Entry<K,V>, java.io.Serializable

一个条目维护一个键和一个值。可使用setValue方法更改该值。此类简化了构建自定义map实现的过程。例如,在方法Map.entrySet().toArray中返回SimpleEntry实例的数组就可能很方便。

21.1 private final K key;

当前条码的键。

21.2 private V value;

当前条目的值。

21.3 public SimpleEntry(K key, V value)

创建代表从指定键到指定值的映射的条目。

源码如下:

public SimpleEntry(K key, V value) {
    this.key   = key;
    this.value = value;
}

21.4 public SimpleEntry(Entry<? extends K, ? extends V> entry)

创建代表与指定条目相同映射的条目。

源码如下:

public SimpleEntry(Entry<? extends K, ? extends V> entry) {
    this.key   = entry.getKey();
    this.value = entry.getValue();
}

21.5 public K getKey()

返回当前条目对应的键。

public K getKey() {
    return key;
}

21.6 public V getValue()

返回当前条目对应的值。

public V getValue() {
    return value;
}

21.7 public V setValue(V value)

用指定的值替换此条目当前对应的值。

public V setValue(V value) {
    V oldValue = this.value;
    this.value = value;
    return oldValue;
}

21.8 public boolean equals(Object o)

比较给定的对象是否与当前条目相等。如果给定的对象也是map的条目,且与当前条目代表相同的键值映射,则返回true。更准确的说,两个条目e1e2满足如下条件则认为其代表相同的映射:

(e1.getKey()==null ?
 e2.getKey()==null :
 e1.getKey().equals(e2.getKey()))
&&
(e1.getValue()==null ?
 e2.getValue()==null :
 e1.getValue().equals(e2.getValue()))

这确保了equals方法在Map.Entry接口的不同实现中正常工作。

源码如下:

public boolean equals(Object o) {
    // 如果指定对象不是Map.Entry实现,则直接返回true
    if (!(o instanceof Map.Entry))
        return false;
    Map.Entry<?,?> e = (Map.Entry<?,?>)o;
    // 使用私有方法eq进行判断键是否相等、值是否相等
    return eq(key, e.getKey()) && eq(value, e.getValue());
}

21.9 public int hashCode()

返回当前map条目的哈希编码值。map条目的哈希编码值被定义为:(e.getKey()==null ? 0 : e.getKey().hashCode()) ^ (e.getValue()==null ? 0 : e.getValue().hashCode())。这也满足了Object的通用规范,即对任意两个条目e1e2来说e1.equals(e2)也就意味着e1.hashCode()==e2.hashCode()

public int hashCode() {
    return (key   == null ? 0 :   key.hashCode()) ^
           (value == null ? 0 : value.hashCode());
}

21.10 public String toString()

返回当前map条目的字符串形式。本实现所返回的字符串形式为:条目的键的字符串形式后面是等于号(=),再后面是条目的值的字符串形式。

public String toString() {
    return key + "=" + value;
}

22. public static class SimpleImmutableEntry<K,V> implements Entry<K,V>, java.io.Serializable

Entry维护了不可变的键和值。该类不支持setValue方法。本类可便于在返回线程安全的键值映射快照方法中使用。

22.1 private final K key;

当前条目所维护的键。

22.2 private final V value;

当前条目所维护的值。

22.3 public SimpleImmutableEntry(K key, V value)

创建一个代表映射的条目,从指定的键指向指定的值。

public SimpleImmutableEntry(K key, V value) {
    this.key   = key;
    this.value = value;
}

22.4 public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry)

创建与指定条目相同映射的条目。

public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
    this.key   = entry.getKey();
    this.value = entry.getValue();
}

22.5 public K getKey()

返回当前条码的键。

public K getKey() {
    return key;
}

22.6 public V getValue()

返回当前条目的值。

public V getValue() {
    return value;
}

22.7 public V setValue(V value)

用指定的值代替当前条目的值(可选操作)。这个实现只是抛出UnsupportedOperationException,因为这个类实现了一个不可变的映射条目。

public V setValue(V value) {
    throw new UnsupportedOperationException();
}

22.8 public boolean equals(Object o)

比较给定的对象是否与当前条目相等。如果给定的对象也是map的条目,且与当前条目代表相同的键值映射,则返回true。更准确的说,两个条目e1e2满足如下条件则认为其代表相同的映射:

(e1.getKey()==null ?
 e2.getKey()==null :
 e1.getKey().equals(e2.getKey()))
&&
(e1.getValue()==null ?
 e2.getValue()==null :
 e1.getValue().equals(e2.getValue()))

这确保了equals方法在Map.Entry接口的不同实现中正常工作。

public boolean equals(Object o) {
    if (!(o instanceof Map.Entry))
        return false;
    Map.Entry<?,?> e = (Map.Entry<?,?>)o;
    return eq(key, e.getKey()) && eq(value, e.getValue());
}

该方法与SimpleEntryequals方法完全相同。

22.9 public int hashCode()

返回当前map条目的哈希编码值。map条目的哈希编码值被定义为:(e.getKey()==null ? 0 : e.getKey().hashCode()) ^ (e.getValue()==null ? 0 : e.getValue().hashCode())。这也满足了Object的通用规范,即对任意两个条目e1e2来说e1.equals(e2)也就意味着e1.hashCode()==e2.hashCode()

public int hashCode() {
    return (key   == null ? 0 :   key.hashCode()) ^
           (value == null ? 0 : value.hashCode());
}

该方法与SimpleEntryhashCode方法完全相同。

22.10 public String toString()

返回当前map条目的字符串形式。本实现所返回的字符串形式为:条目的键的字符串形式后面是等于号(=),再后面是条目的值的字符串形式。

public String toString() {
    return key + "=" + value;
}

该方法与SimpleEntrytoString方法完全相同。