AbstractMap
此类提供了Map
接口的框架实现,以最大程度减少实现此接口所需的工作量。
要实现一个不可修改的map,编程者只需扩展这个类,并为entrySet
方法提供一个实现,该方法返回map映射的set视图。通常,返回的set将基于AbstractSet
实现。此set不应支持add
或remove
方法,其迭代器不应支持remove
方法。
要实现一个可修改的map,编程者必须额外重写这个类的put
方法(否则会抛出一个UnsupportedOperationException
),并且由entrySet().iterator()
返回的迭代器必须额外实现它的remove
方法。
根据Map
接口规范中的建议,编程者通常应提供一个void
(无参数)构造函数和一个参数为map的构造函数。
此类中每个非抽象方法的文档都详细描述了其实现。如果正在实现的map允许更有效的实现,则这些方法中的每一个都可能被重写。
本类是Java集合框架的成员。
唯一的构造器。(子类构造函数调用,通常是隐式的。)
protected AbstractMap() {
}
返回当前map的元素个数,本实现是返回的entrySet().size()
。
public int size() {
return entrySet().size();
}
判断当前map是否为空。
public boolean isEmpty() {
return size() == 0;
}
本实现遍历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;
}
本实现遍历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;
}
本实现遍历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;
}
向当前map中添加(或更新)键值对。此默认实现中是抛出UnsupportedOperationException
,所以如果想实现可修改的Map
需要重写该方法。
源码如下:
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
此实现遍历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;
}
此实现遍历指定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());
}
本实现调用entrySet().clear()
以删除当前map内的所有映射。
注意,如果entrySet
不支持clear
操作,则本实现将抛出UnsupportedOperationException
。
public void clear() {
entrySet().clear();
}
在第一次请求此视图时,这些字段中的每个字段都会初始化为包含相应视图的实例。视图是无状态的,因此没有理由创建多个视图。
由于在访问这些字段时没有执行同步,因此使用这些字段的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;
}
在第一次请求此视图时,这些字段中的每个字段都会初始化为包含相应视图的实例。视图是无状态的,因此没有理由创建多个视图。
此实现返回一个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;
}
此实现返回一个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;
}
抽象方法,获取当前map的条目的集合。
比较指定对象是否与当前map相等。如果给定的对象也是一个map并且这两个map表示相同的映射,则返回true
。更正式地说,如果两个mapm1
、m2
满足m1.entrySet().equals(m2.entrySet())
,则m1
和m2
表示相同的映射。这确保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;
}
返回此map的哈希码值。map的哈希码定义为map的entrySet()
视图中每个条目的哈希码之和。这确保对于任何两个map m1
和m2
来说m1.equals(m2)
意味着m1.hashCode()==m2.hashCode()
,这也是Object
的hashCode
通用规范要求。
此实现迭代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;
}
返回此map的字符串表示形式。字符串表示法由键值映射列表组成,其顺序为map的entrySet
视图的迭代器返回,并用大括号"{}"
括起来。相邻映射由字符", "
(逗号和空格)分隔。每个键值映射都呈现为键后跟一个等号("="
),后跟对应的值。键和值通过String
的valueOf(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(' ');
}
}
返回此AbstractMap
实例的浅层副本:键和值本身不会被克隆。
源码如下:
protected Object clone() throws CloneNotSupportedException {
// 调用Object类的clone方法获取一个AbstractMap实例
AbstractMap<?,?> result = (AbstractMap<?,?>)super.clone();
// 将keySet和values均置空
result.keySet = null;
result.values = null;
return result;
}
SimpleEntry
和SimpleImmutableEntry
的实用方法。测试是否相等,检查是否为空。
源码如下:
private static boolean eq(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
一个条目维护一个键和一个值。可使用setValue
方法更改该值。此类简化了构建自定义map实现的过程。例如,在方法Map.entrySet().toArray
中返回SimpleEntry
实例的数组就可能很方便。
当前条码的键。
当前条目的值。
创建代表从指定键到指定值的映射的条目。
源码如下:
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
创建代表与指定条目相同映射的条目。
源码如下:
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
返回当前条目对应的键。
public K getKey() {
return key;
}
返回当前条目对应的值。
public V getValue() {
return value;
}
用指定的值替换此条目当前对应的值。
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
比较给定的对象是否与当前条目相等。如果给定的对象也是map的条目,且与当前条目代表相同的键值映射,则返回true
。更准确的说,两个条目e1
和e2
满足如下条件则认为其代表相同的映射:
(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());
}
返回当前map条目的哈希编码值。map条目的哈希编码值被定义为:(e.getKey()==null ? 0 : e.getKey().hashCode()) ^ (e.getValue()==null ? 0 : e.getValue().hashCode())
。这也满足了Object
的通用规范,即对任意两个条目e1
和e2
来说e1.equals(e2)
也就意味着e1.hashCode()==e2.hashCode()
。
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
返回当前map条目的字符串形式。本实现所返回的字符串形式为:条目的键的字符串形式后面是等于号(=
),再后面是条目的值的字符串形式。
public String toString() {
return key + "=" + value;
}
本Entry
维护了不可变的键和值。该类不支持setValue
方法。本类可便于在返回线程安全的键值映射快照方法中使用。
当前条目所维护的键。
当前条目所维护的值。
创建一个代表映射的条目,从指定的键指向指定的值。
public SimpleImmutableEntry(K key, V value) {
this.key = key;
this.value = value;
}
创建与指定条目相同映射的条目。
public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
返回当前条码的键。
public K getKey() {
return key;
}
返回当前条目的值。
public V getValue() {
return value;
}
用指定的值代替当前条目的值(可选操作)。这个实现只是抛出UnsupportedOperationException
,因为这个类实现了一个不可变的映射条目。
public V setValue(V value) {
throw new UnsupportedOperationException();
}
比较给定的对象是否与当前条目相等。如果给定的对象也是map的条目,且与当前条目代表相同的键值映射,则返回true
。更准确的说,两个条目e1
和e2
满足如下条件则认为其代表相同的映射:
(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());
}
该方法与SimpleEntry
的equals
方法完全相同。
返回当前map条目的哈希编码值。map条目的哈希编码值被定义为:(e.getKey()==null ? 0 : e.getKey().hashCode()) ^ (e.getValue()==null ? 0 : e.getValue().hashCode())
。这也满足了Object
的通用规范,即对任意两个条目e1
和e2
来说e1.equals(e2)
也就意味着e1.hashCode()==e2.hashCode()
。
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
该方法与SimpleEntry
的hashCode
方法完全相同。
返回当前map条目的字符串形式。本实现所返回的字符串形式为:条目的键的字符串形式后面是等于号(=
),再后面是条目的值的字符串形式。
public String toString() {
return key + "=" + value;
}
该方法与SimpleEntry
的toString
方法完全相同。