Map
Map 是一个将 keys 映射到 values 的对象。一个 map 对象不能包含重复的 keys. 每一个 key 最多只能映射到一个对象。
Map 这个接口是为了取代 Dictionary 这个抽象类的,更直白的说,就是拿一个接口去取代之前抽象类。
three collection views
Map 接口提供了三套查看方法来查看map所包含的内容。
- 查看它所包含的所有 keys (view as a set of keys)
- 查看它所包含的所有 values(view as collection of values)
- 查看它所包含的键-值映射。(view as a set of key-value mappings)
element orders
Map 接口是以键值对的形式存储的。所以它的接口定义和 Collection 是有区别的。比如Collection 接口里面就有 **Iterator iterator(); ** 这个方法,但Map就不能有,为什么呢?看看 Iterator 接口的定义就知道了。
package java.util;
import java.util.function.Consumer;
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> var1) {
Objects.requireNonNull(var1);
while(this.hasNext()) {
var1.accept(this.next());
}
}
}
看到没?next() 方法返回的是单个元素。Map 存储的是键值对,所以玩不转了。为此,jdk 定义了适用于Map排序的接口: SortedMap.
package java.util;
import java.util.Map.Entry;
public interface SortedMap<K, V> extends Map<K, V> {
Comparator<? super K> comparator();
SortedMap<K, V> subMap(K var1, K var2);
SortedMap<K, V> headMap(K var1);
SortedMap<K, V> tailMap(K var1);
K firstKey();
K lastKey();
Set<K> keySet();
Collection<V> values();
Set<Entry<K, V>> entrySet();
}
光排序还是不够的,还需要提供一种按照顺序访问的接口:NavigableMap
package java.util;
import java.util.Map.Entry;
public interface NavigableMap<K, V> extends SortedMap<K, V> {
Entry<K, V> lowerEntry(K var1);
K lowerKey(K var1);
Entry<K, V> floorEntry(K var1);
K floorKey(K var1);
Entry<K, V> ceilingEntry(K var1);
K ceilingKey(K var1);
Entry<K, V> higherEntry(K var1);
K higherKey(K var1);
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
NavigableMap<K, V> descendingMap();
NavigableSet<K> navigableKeySet();
NavigableSet<K> descendingKeySet();
NavigableMap<K, V> subMap(K var1, boolean var2, K var3, boolean var4);
NavigableMap<K, V> headMap(K var1, boolean var2);
NavigableMap<K, V> tailMap(K var1, boolean var2);
SortedMap<K, V> subMap(K var1, K var2);
SortedMap<K, V> headMap(K var1);
SortedMap<K, V> tailMap(K var1);
}
并不是所有的Map都要保证按照一定顺序存储键值对的。比如说HashMap就没有。看看它的类:
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
//省略
}
看到没有,没有实现 NavigableMap 和 SortedMap 接口。
而TreeMap就有顺序一说了。
public class TreeMap<K, V> extends AbstractMap<K, V> implements NavigableMap<K, V>, Cloneable, Serializable {
//省略
}
Note
清注意,如果Map的 key 是一个对象,而这个对象是可变更的,那就要格外小心了。如果作为key的对象的值发生了变更,这个变更影响了equals方法比较。变更之后,这个对象在Map中成为未指定映射的行为。这种禁令的一个特殊情况是,Map 不允许将自身作为键进行包含。 虽然允许Map将自身包含为值,但建议格外小心:此类Map上不再明确定义等值和哈希代码方法。
Map 所有实现类的构造函数约定
所有通用 Map 实现类都应提供两个"标准"构造函数:
- 创建空映射的 void(无参数)构造函数,
- 传一个已有的map对象,而这个传入map的key,value类型和所构造的map的key,value类型是一样的。
文字太抽象了,以HashMap为例子,上代码吧:
/**
* Constructs an empty {@code HashMap} with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* Constructs a new {@code HashMap} with the same mappings as the
* specified {@code Map}. The {@code HashMap} is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified {@code Map}.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
实际上,后一个构造函数允许用户复制任何Map,从而生成所需类的等效Map。
接口定义中无法实施此建议(因为接口不能包含构造函数),但 JDK 中的所有通用映射实现都符合。
此接口中包含的"破坏性" 方法(即修改其操作的 Map 的方法)被指定为引发 UnsupportedOperationException(如果此map不支持该操作)。 如果是这种情况,如果调用对map没有影响,则这些方法可能(但不是必需的)引发 UnsupportedOperationException。 例如,在不可修改的map上调用 putAll(Map) 方法,如果映射为"叠加"为空,则可能会引发异常,但不是必需的。
某些Map实现对可能包含的键和值具有限制。 例如,某些实现禁止键和值为 null,有些实现对其 keys 的类型有限制。 尝试插入不符合资格的键或值会引发 unchecked exception ,通常是 NullPointerException 或 ClassCastException。 尝试查询是否存在不符合资格的键或值可能会引发异常,或者可能只是返回 false;否则,可能会引发异常。一些实现将表现出前一种行为,有些将展示后者。 更一般地,尝试对不符合资格的键或值执行操作,而该键或值的完成不会导致将不合格的元素插入到映射中,可能会引发异常,或者可能会成功。接口规范中将此类异常标记为可选的。
集合框架接口中的许多方法都是根据 **Object.equal(Object) 等值方法定义的。 例如, containsKey(Object) 方法的规范说:
当且仅当map包含一个 key, 而这个key满足:
key == null ? k == null : key.equals(k)
则返回true
此规范不应被解释为暗示调用 Map.containsKey 使用非null 参数键 key 导致 对任意的key 都将执行 key.equals(k)。实现是没有约束的,你可以根据情况做优化,例如,通过比较两个键的 hashCode,可以避免 equals 调用。Object.hashCode()的规范确保了,如果两个对象的hashCode不想等,那么他们肯定不相等。更一般地,各种集合框架接口的实现可以自由地利用底层 Object 方法的指定行为,只要实现者认为它合适。
如果map 直接或者间接地包含了自身引用。导致 map 在执行第归遍历操作的时候引发异常。比如: clone(), equals(), hashCode() 和 toString() 方法,实现的时候可以根据需要自定义方案处理,但大多数当前实现不这么做。
Unmodifiable Maps
可以用以下静态工厂方法创建一个不可修改的map。
- Map.of() 系列
- Map.ofEntries 系列
- Map.copyOf
由这些方法创建的代码映射实例具有以下特征: - 它们在结构上是不变的。key 和 value 不能添加,删除或更新。调用任何mutator方法都会导致 UnsupportedOperationException抛出异常。但是,如果所包含的 key 或 value 本身是可变的,则可能会导致Map行为不一致或其内容似乎发生变化。
- 它们不允许null键和值。尝试使用null键或值创建它们会 导致NullPointerException。
- 如果所有键和值都是可序列化的,则它们是可序列化的。
- 他们在创建时拒绝重复的key。传递给静态工厂方法的重复键导致IllegalArgumentException。
- 映射的迭代顺序不确定,可能会发生变化。
- 它们是基于价值的。调用者不应对返回实例的身份做任何假设。工厂可以自由创建新实例或重用现有实例。因此,在这些实例上的身份敏感操作(引用相等(==),身份哈希码和同步)不可靠,应避免。
- 它们按照“ 序列化表格” 页面上的指定进行 序列化。