简介
Map
是一个接口,代表的是将键映射到值的对象。一个映射不能包含重复的键,每个键最多只能映射到一个值。
Map
接口提供了三种collection
视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序 定义为迭代器在映射的 collection
视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap
类;另一些映射实现则不保证顺序,如 HashMap
类。
下面,我们去通过源码中看一看Map
都给我们提供了哪些功能,以及一些方法的用法。
增 or 改:
/**
* 将指定的值与此映射中的指定键关联。
* 如果此映射以前包含一个该键的映射关系,则用指定值替换旧值
* @param key 与指定值关联的键
* @param value 与指定键关联的值
* @return 以前与 key 关联的值,如果没有针对 key 的映射关系,则返回 null。
*/
V put(K key, V value);
/**
* 从指定映射中将所有映射关系复制到此映射中
* @param m 要存储在此映射中的映射关系
*/
void putAll(Map<? extends K, ? extends V> m);
可以看出,在 Java 8 之前提供了这两个向映射中添加映射关系的方法,这两个方法我们已经耳熟能详,下面我们来看一下查看元素的方法。
查:
/**
* 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
* @param key 要返回其关联值的键
* @return 指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
*/
V get(Object key);
这里的前提是你必须知道映射中的键,才能获取映射中的值。但是我们在前面说过,Map
接口提供了三个collection
的视图,我们可以使用这些视图来去获取Map
中的元素
/**
* 返回此映射中包含的键的 Set 视图。
* @return 此映射中包含的键的 set 视图
*/
Set<K> keySet();
/**
* 返回此映射中包含的值的 Collection 视图。
* @return 此映射中包含的值的 collection 视图
*/
Collection<V> values();
/**
* 返回此映射中包含的映射关系的 Set 视图。
* @return 此映射中包含的映射关系的 set 视图
*/
Set<Entry<K, V>> entrySet();
当然,还有在 Java 8 新增的forEach
方法也可以遍历获取Map
中的值
/**
* 遍历集合,这里的参数是一个函数式接口,可以结合Lambda表达式去优雅的使用
* @param action 进行的操作,函数式接口
*/
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
//其实本质上还是用entrySet()获取键值对后进行遍历的
for (Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
我们可以这样去使用forEach
方法:
Map<String, String> map = new HashMap<>();
map.forEach((k, v) -> System.out.println("key:value = " + k + ":" + v));
当然,我们可以这样的去优雅去遍历一个集合:
//获取key视图
map.keySet().forEach(s -> System.out.println("Key:Value=" + s + ":" + map.get(s)));
//获取键值对视图
map.entrySet().forEach(entry -> System.out.println("Key:Value=" + entry.getKey() + ":" + entry.getValue);
//获取值视图
map.values.forEach(s -> System.out.println("Value" + s));
下面我们来看一下Map
的删:
删:
/**
* 如果存在一个键的映射关系,则将其从此映射中移除
* @param key 从映射中移除其映射关系的键
* @return 以前与 key 关联的值;如果没有 key 的映射关系,则返回 null。
*/
V remove(Object key);
/**
* 从此映射中移除所有映射关系,该方法被调用后,该映射将为空。
*/
void clear();
hashCode()
和equals()
也在Map
中被重新定义了:
/**
* 比较指定的对象与此映射是否相等。如果给定的对象也是一个映射,并且这两个映射表示相同的映射关系,则返回 true。更确切地讲,
* 如果 m1.entrySet().equals(m2.entrySet()),则两个映射 m1 和 m2 表示相同的映射关系。这可以确保 equals 方法在不同的 Map 接口实现间运行正常。
* @param o
* @return
*/
@Override
boolean equals(Object o);
/**
* 返回此映射的哈希码值。映射的哈希码定义为此映射 entrySet() 视图中每个项的哈希码之和。
* 这确保 m1.equals(m2) 对于任意两个映射 m1 和 m2 而言,都意味着 m1.hashCode()==m2.hashCode(),正如 Object.hashCode() 常规协定的要求。
* @return
*/
@Override
int hashCode();
在这里只是定义了一个接口,而在具体的实现时,必须遵循这里所规定的一些规则,映射的哈希码定义为此映射 entrySet() 视图中每个项的哈希码之和。这样才能保证equals
方法在不同的 Map
接口实现间运行正常。
在 Java 8 之后,新增了一些default方法可以配合lambda表达式去使用,我们一起来看一下这几个方法:
JDK1.8新特性
/**
* 根据映射的键进行排序
*/
public static <K extends Comparable<? super K>, V> Comparator<Entry<K,V>> comparingByKey() {
return (Comparator<Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
/**
* 通过指定的比较器根据映射的键进行排序
*/
public static <K, V> Comparator<Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
首先来说的是Map
的子接口Entry
中的comparingByKey()
方法,这个方法所起到的作用是按照映射的键进行排序,我们接下来来看一下怎么取用:
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
map.put("E","test5");
map.put("D","test4");
map.put("C","test3");
Stream<Map.Entry<String, String>> sorted = map.entrySet().stream().sorted(Map.Entry.comparingByKey());
Stream<Map.Entry<String, String>> sorted2 = map.entrySet().stream().sorted(Map.Entry.comparingByKey(String::compareTo));
sorted.forEach(entry -> System.out.println(entry.getValue()));
System.out.println("===============");
sorted2.forEach(entry -> System.out.println(entry.getValue()));
}
}
输出结果为:
test1
test2
test3
test4
test5
===============
test1
test2
test3
test4
test5
可以看到,这是被排序后的结果,至于comparingByValue
方法,其实用法和comparingByKey()
基本上一样,所以这里不再多做解说,forEach的用法,我们在前面已经进行了了解,下面,我们来看一下getOrDefault()
方法
/**
* 如果映射中存在于key相对应的value,则返回这个value,否则返回defaultValue
* @param key 指定值的键,如果该value不存在,返回defaultValue
* @param defaultValue 如果指定键的值不存在,返回这个值
* @return 如果映射中存在于key相对应的value,则返回这个value,否则返回defaultValue
*/
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
下面,我写了一个小demo来让大家看看这个方法怎么用
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
String value = map.getOrDefault("A", "test2");
System.out.println(value);
String defaultValue = map.getOrDefault("C", "test1");
System.out.println(defaultValue);
}
输出的结果为:
test1
test1
可以看出,第一个获取到的是键“A”对应的值“test1”,第二次调用获取的是defaultValue—-“test1”。下面,我们来看一下replace
家族的一些成员:
/**
* 对映射中的所有键值对执行计算,并将返回结果作为value覆盖
* map.replaceAll((k,v)->((String)k).length());
* @param function 执行的操作,函数式接口
*/
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
...
}
/**
* 当且仅当 key 存在,并且对应值与 oldValue 不相等,才用 newValue 作为 key 的新相关联值,返回值为是否进行了替换。
* @param key 与指定值相关联的键
* @param oldValue 预期与指定键相关联的值
* @param newValue 与指定键相关联的值
* @return 如果该值被替换,返回true
*/
default boolean replace(K key, V oldValue, V newValue) {
...
}
/**
* 只有当目标映射到某个值时,才能替换指定键的条目。
* @param key 与指定值相关联的键
* @param value 与指定键相关联的值
* @return 与指定键相关联的上一个值,如果没有键的映射,返回null
*/
default V replace(K key, V value) {
...
}
接下来,我写了一个小demo来看看这三个方法的用法:
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
map.replaceAll((s, s2) -> {
return s + s2;
});
printMap(map);
map.replace("A","test1");
printMap(map);
map.replace("A","test2","test1");
printMap(map);
map.replace("A","test1","test2");
printMap(map);
}
public static void printMap(Map<String,String> map){
map.forEach((key, value) -> System.out.print(key + ":" + value + " "));
System.out.println();
}
打印结果:
A:Atest1 B:Btest2
A:test1 B:Btest2
A:test1 B:Btest2
A:test2 B:Btest2
一切正如我们所想象的那般发展,接下来,我们来看一下compute
三兄弟。
/**
* 如果指定的键尚未与值相关联(或映射到null),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非null 。
* @param key 指定值与之关联的键
* @param mappingFunction 计算值的函数
* @return 与指定键相关联的当前(现有或计算)值,如果计算值为空,则为null
*/
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
...
}
/**
* 如果指定的key的值存在且非空,则尝试计算给定键及其当前映射值的新映射。
* @param key 指定值与之关联的键
* @param remappingFunction 计算值的函数
* @return 与指定键相关的新值,如果没有则为null
*/
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
...
}
/**
* 尝试计算指定key及其当前映射值的映射(如果没有当前映射,则null )。
* @param key 指定值与之关联的键
* @param remappingFunction 计算值的函数
* @return 与指定键相关的新值,如果没有则为null
*/
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
...
}
现在,我们来看一下,这三个方法是怎么用的,以及他们的不同。
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
map.compute("A", (key, value) -> { return key + value;});
printMap(map);
//因为,集合中存在“A”,所以这里没有进行相应的操作
map.computeIfAbsent("A", (key) -> { return key + 2;});
printMap(map);
//这里因为集合中不存在“C”,所以进行了赋值的操作
map.computeIfAbsent("C", (key) -> { return key + 2;});
printMap(map);
//这里由于集合存在“A”,根据方法定义,会计算后返回给原值
map.computeIfPresent("A", (key, value) -> { return key + value;});
printMap(map);
//这里由于不存在“D”,根据方法定义,不做任何操作
map.computeIfPresent("D", (key, value) -> { return key + value;});
printMap(map);
}
public static void printMap(Map<String,String> map){
map.forEach((key, value) -> System.out.print(key + ":" + value + " "));
System.out.println();
}
输出结果:
A:Atest1 B:test2
A:Atest1 B:test2
A:Atest1 B:test2 C:C2
A:AAtest1 B:test2 C:C2
A:AAtest1 B:test2 C:C2
那么,最后剩下的还有一个类似于compute
的merge
方法以及remove
方法和putIfAbsent
方法,接下来我们来看看这几个方法。
/**
* 如果key在集合中的value为空或则键值对不存在,则用参数value覆盖
* @param key 如果key存在且不为null,返回key对应的value,如果不存在,调用put(key,value)
* @param value 如果key对应的值不存在或者为null,将该value与key进行对应
* @return 返回的是被替代的值
*/
default V putIfAbsent(K key, V value) {
...
}
/**
* key 与 value 都匹配时才删除。
* @param key 被删除的映射关系的key
* @param value 被删除的映射关系的value
* @return 返回的是否删除成功
*/
default boolean remove(Object key, Object value) {
...
}
/**
* 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联。
* @param key 结合值与之关联的键
* @param value 要与与key相关联的现有值合并的非空值,或者如果没有现有值或空值与key相关联,则与该key相关联
* @param remappingFunction 重新计算值(如果存在)的功能
* @return 与指定键相关联的新值,如果没有值与该键相关联,则返回null
*/
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
}
接下来,我们接着来看一个例子:
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
map.putIfAbsent("A","test2");
map.putIfAbsent("C","test3");
printMap(map);
map.remove("A","test1");
printMap(map);
map.merge("A","test1",(oldValue, newValue) ->{
return oldValue + newValue;
} );
printMap(map);
map.merge("A","test4",(oldValue, newValue) ->{
return newValue;
} );
printMap(map);
}
输出的是:
A:test1 B:test2 C:test3
B:test2 C:test3
A:test1 B:test2 C:test3
A:test4 B:test2 C:test3
到这里,Map
的讲解就要和大家告一段落了,下篇AbstractMap
再见~
原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。