jdk1.8.0_144  

  Map是Java三种集合中的一种位于java.util包中,Map作为一个接口存在定义了这种数据结构的一些基础操作,它的最终实现类有很多:HashMap、TreeMap、SortedMap等等,这些最终的子类大多有一个共同的抽象父类AbstractMap。在AbstractMap中实现了大多数Map实现公共的方法。本文介绍Map接口定义了哪些方法,同时JDK8又新增了哪些。

  Map翻译为“映射”,它如同字典一样,给定一个key值,就能直接定位value值,它的存储结构为“key : value"形式,核心数据结构在Map内部定义了一个接口——Entry,这个数据结构包含了一个key和它对应的value。首先来窥探Map.Entry接口定义了哪些方法。

interface Map.Entry<K, V>

K getKey()

  获取key值。

V getValue()

  获取value值。

V setValue(V value)

  存储value值。

boolean equals(Object o)

int hashCode()

  这两个方法我在《万类之父——Object》中提到过,这是Object类中的方法,这两个方法通常是同时出现,也就是说要重写equals方法时为了保证不出现问题往往需要重写intCode方法。而重写equals则需要满足5个规则(自反性、对称性、传递性、一致性、非空性)。当然具体是如何重写的,此处作为接口并不做解释而是交由它的子类完成。

public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey()

public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue()

public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp)

public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp)

  这四个方法放到一起是因为这都是JDK8针对Map更为简单的排序新增加的泛型方法,这里的泛型方法看似比较复杂,我们针对第一个方法先来简单回顾一下泛型方法。

  一个泛型方法的基本格式就是泛型参数列表需要定义在返回值前。这个方法的返回值返回的是Comparator<Map.Entry<K, V>>,也就是说它的泛型参数列表是“<K extends Comparable<? super K>, V>”,有两个泛型参数K和V。参数K需要实现Comparable接口。

  既然这是JDK8为Map排序新增的方法,那它是如何使用的呢? 不妨回忆下JDK8以前对Map是如何排序的:

 /**
* Sort a Map by Keys.——JDK7
* @param map To be sorted Map.
* @return Sorted Map.
*/
public Map<String, Integer> sortedByKeys(Map<String, Integer> map) {
List<Map.Entry<String, Integer>> list = new LinkedList<>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
Map<String, Integer> linkedMap = new LinkedHashMap<>();
Iterator<Map.Entry<Strin g, Integer>> iterator = list.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
linkedMap.put(entry.getKey(), entry.getValue());
} return linkedMap;
}

  从JDK7版本对Map排序的代码可以看到,首先需要定义泛型参数为Map.Entry类型的List,利用Collections.sort对集合List进行排序,再定义一个LinkedHashMap,遍历集合List中的元素放到LinkedHashMap中,也就是说并没有一个类似Collections.sort(Map, Comparator)的方法对Map集合类型进行直接排序。JDK8对此作了改进,通过Stream类对Map进行排序。

 /**
* Sort a Map by Keys.——JDK8
* @param map To be sorted Map.
* @return Sorted Map.
*/
public Map<String, Integer> sortedByKeys(Map<String, Integer> map) {
Map<String, Integer> result = new LinkedHashMap<>();
map.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(x -> result.put(x.getKey(), x.getValue()));
return result;
}

  可见代码量大大减少,简而言之,这四个方法是JDK8利用Stream类和Lambda表达式弥补Map所缺少的排序方法。

  comparingByKey() //利用key值进行排序,但要求key值类型需要实现Comparable接口。

  comparingByValue() //利用value值进行排序,但要求key值类型需要实现Comparable接口。

  comparingByKey(Comparator) //利用key值进行排序,但key值并没有实现Comparable接口,需要传入一个Comparator比较器。

  comparingByValue(Comparator) //利用value值进行排序,但value值并没有实现Comparable接口,需要传入一个Comparator比较器。

  再多说一句,Comparator采用的是策略模式,即不修改原有对象,而是引入一个新的对象对原有对象进行改变,此处即如果key(或value)并没有实现Comparable接口,此时可在不修改原有代码的情况下传入一个Comparator比较器进行排序,对原有代码进行修改是一件糟糕的事情。

  参考链接:《JDK8的新特性——Lambda表达式》《似懂非懂的Comparable与Comparator》

Map.Entry接口中定义的方法到此结束,下面是Map接口中锁定义的方法。

int size()

  返回Map中key-value键值对的数量,最大值是Integer.MAX_VALUE(2^31-1)。

boolean isEmpty()

  Map是否为空,可以猜测如果size() = 0,Map就为空。

boolean containsKey(Object key)

  Map是否包含key键值。

boolean containsValue(Object value)

  Map是否包含value值。

V get(Object key)

  通过key值获取对应的value值。如果Map中不包含key值则返回null,也有可能该key值对应的value值本身就是null,此时要加以区别的话可以先使用containsKey方法判断是否包含key值。

V put(K key, V value)

  向Map中存入key-value键值对,并返回插入的value值。

  Map从JDK5过后就改为了泛型类,get方法的参数不是泛型K,而是一个Object对象呢?包括上面的containsKey(Object)和containsValue(Object)参数也是Object而不是泛型。在这个地方似乎是使用泛型更加合适。思考以下场景:

  1. 最开始我写了一段代码,定义HashMap<String, String>,定义HashMap<String, String>,此时我put("a", "a"),同时我通过get("a")获取值。
  2. 写着写着,我发现我应该定义为HashMap<Integer, String>,此时IDE 会自动的在put("a", "a")方法报错,因为Map的泛型参数类型key修改为了Integer,我能很好的发现它并改正。但是,我的get("a")并不会有任何提示,因为它的参数是Object能接收任意类型的值,假如我get方法同样使用了泛型此时IDE就会提醒我这个地方参数类型不对,应该是Integer类型。那么为什么会出现get方法是使用Object类型,而不是泛型呢?难道JDK的作者没有想到这一点吗?明明能在编译时就能发现的问题,为什么要在运行时再去判断?

  这个问题在StackOverflow上也有讨论,链接:https://stackoverflow. com/questions/1926285/why-does-hashmapcontainskey-take-an-parameter-of-type-objecthttp://smallwig.blogspot.com/2007/12/why-does-setcontains-take-object-not-e.html 我大致翻译了一下这可能有以下几个方面的原因: 

  1.这是为了保证兼容性 泛型是在JDK1.5才出现的,而HashMap则是在JDK1.2才出现,在泛型出现的时候伴随着不少兼容性问题,为了保证其兼容性不得不做了一些处理,例如泛型类型的擦除等等。假设在JDK1.5之前存在以下代码:

 HashMap hashMap = new HashMap();
ArrayList arrayList = new ArrayList();
hashMap.put(arrayList, "this is list");
System.out.println(hashMap.get(arrayList));
LinkedList linkedList = new LinkedList();
System.out.println(hashMap.get(linkedList));

  这段代码在不使用泛型的时候能运行的很好,如果此时get方法中的参数变成了泛型,而不是Object,那么此时hashMap.get(linkedList)这句话将会在编译时出错,因为它不是ArrayList类型。

  2.无法确定Key的类型。这里有一个例子:

 public class HashMapTest {
public static void main(String[] args) {
HashMap<SubFoo, String> hashMap = new HashMap<>();
//SubFoo是Foo类的子类
test(hashMap); //编译时出错
} public static void test(HashMap<Foo, String> hashMap) { //参数为HashMap,key值是Foo类,但是不能接收它的子类
System.out.println(hashMap.get(new Foo()));
}
}

  上面这种情况把test方法中的参数类型修改为HashMap<? extends Foo, String>即可。但是这是在get方法的参数类型是Object情况下才正确,如果get方法的参数类型是泛型,那它对于“? extends Foo”是一无所知的,换句话说,编译器不知道它应该接收Foo类型还是SubFoo类型,甚至是SubSubFoo类型。对于第二个假设,不少网友指出,get方法的参数类型可以是“<T extends E>”,这就能避免第二个问题了。

  在国外网友的讨论中,我还是比较倾向于第一种兼容性问题,毕竟泛型相对来说较晚出现,对于作者John也说过,他们尝试把它泛型化,但泛型化过后产生了一系列的问题,这不得不使得他们放弃将其泛型化。其实在源码的get方法注释中能看到put以前也是Object类型,在泛型出现过后,put方法能成功的改造成泛型,而get由于要考虑兼容性问题不得不放弃将它泛型化。

V remove(Object key)

  删除Map中的key-value键值对。

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

  这个方法的参数是一个Map,将传入的Map全部放入此Map中,当然对参数Map有要求,“? extends K”意味着传入的Map其key值需要是此Map的key或者是子类,value同理。

void clear()

  移除Map中所有的key-value键值对。

Set<K> keyset()

  返回key的set集合,注意set是无序且不可存储重复的值,当然Map中也不可能存在重复的key值,也没有有序无序一说。其实这个方法的运用还是有点意思的,这会涉及到Java对象引用相关的一些知识。

 Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
System.out.println(map.keySet()); //output: [a, b]
Set<String> sets = map.keySet();
sets.remove("a");
System.out.println(map.keySet()); //output: [b]
sets.add("c"); //output: throws UnsupportedOperationException
System.out.println(map.keySet());

  第4行的输出的是Map中key的set集合,即“[a,b]” 。

  接着创建一个set对象指向map.keySet()方法返回set的集合,并且通过这个set对象删除其中的“a”元素。此时再来通过map.keySet()方法打印key的集合,会发现此时打印“[b]”。这是因为我们在虚拟机栈上定义的sets对象其指针指向的是map.keySet()返回的对象,也就是说这两者指向的是同一个地址,那么只要任一一个对其改变都会影响这个对象本身,这也是Map接口对这个方法的定义,同时Map接口对该方法还做了另外一个限制,不能通过keySet()返回的Set对象对其进行add操作,此时将会抛出UnsupportedOperationException异常,原因很简单如果给Set对象add了一个元素,相对应的Map的key有了,那么它对应的value值呢?

Collection<V> values()

  返回value值的Collection集合。这个集合就直接上升到了集合的顶级父接口——Collection。为什么不是Set对象了呢?原因也很简单,key值不能重复返回Set对象很合理,但是value值肯定可以重复,返回Set对象显然不合适,如果仅仅返回List对象,那也不合适,索性返回顶级父接口——Collection。

Set<Map.Entry<K, V>> entrySet()

  返回Map.Entry的Set集合。

boolean equals(Object o)

int hashCode()

  equals在Object类中只是用“==”简单的实现,对于比较两个Map是否值相等显然需要重写equals方法,重写equals方法通常需要重写hashCode方法。重写equals方法需要遵守5个原则:自反性、对称性、传递性、一致性、非空性。在满足了这个几个原则后还需要满足:两个对象equals比较相等,它们的hashCode散列值也一定相等;但hashCode散列值相等,两个对象equals比较不一定相等。

default V getOrDefault(Object key, V defaultValue)

  这个方法是JDK8才出现的,并且使用了JDK8的一个新特性,在接口中实现一个方法,叫做default方法,和抽象类类似,default方法是一个具体的方法。这个方法主要是弥补在编码过程中遇到的这样场景:如果一个Map不存在某个key值,则存入一个value值。以前是会写一个判断使用contanisKey方法,现在则只需要一句话就可以搞定map.put("a", map.getOrDefault("a", 2)); 它的实现也很简单,就是判断key值在Map中是否存在,不存在则存入getOrDefault中的defaultValue参数,存在则再存入一次以前的value参数。 (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue;

default void forEach(BiConsumer<? super K, ? super V> action)

  这个方法也是JDK8新增的,为了更方便的遍历,这个方法几乎新增在JDK8的集合中,使用这个新的API能方便的遍历集合中的元素,这个方法的使用需要结合Lambda表达式:map.forEach((k, v) -> System.out.println("key=" + k + ", value=" + v))

default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

  替换Map中的value值,Lambda表达式作为参数,例如:

 map.replaceAll((k, v) -> 10);    //将Map中的所有值替换为10
map.replaceAll((k, v) -> { //如果Map中的key值等于a,其value则替换为10
if (k.equals("a")) {
return 10;
}
return v;
});

default V putIfAbsent(K key, V value)

  在ConcurrentHashMap中也有一个putIfAbsent方法,那个方法指的key值不存在就插入,存在则不插入。JDK8中在Map中直接也新增了这个方法,这个方法ConcurrentHashMap#putIfAbsent含义相同,这个方法等同于:

 if (!map.containsKey(key, value)) {
map.put(key, value);
} else {
map.get(key);
}

  在之前提到了一个方法和这个类似——getOrDefault。注意不要搞混了,调用putIfAbsent会直接插入,而getOrDefault不会直接插入到Map中。

default boolean remove(Object key, Object value)

  原来的remove方法是直接传递一个key从Map中移除对应的key-value键值对。新增的方法需要同时满足key和value同时在Map有对应键值对时才删除

default boolean replace(K key, V oldValue, V newValue)

  和replaceAll类似,当参数中的key-oldValue键值对在Map存在时,则使用newValue替换oldValue。

default V replace(K key, V value)

  这个方法是上面方法的重载,不会判断key值对应的value值,而是直接使用value替换key值原来对应的值。

default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

  如果Map中不存在key值,则调用Lambda表达式中的函数主体计算value值,再放入Map中,下次再获取的时候直接从Map中获取。这其实在Map实现本地缓存中随处可见,这个方法类似于下列代码:

 if (map.get(key) == null) {
value = func(key); //计算value值
map.put(key, value);
}
return map.get(key);

default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

  这个方法给定一个key值,通过Lambda表达式可计算自定义key和value产生的新value值,如果新value值为null,则删除Map中对应的key值,如果不为空则用新的替换旧的值。

default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

  这个方法是上面两个方法的结合,有同时使用到上面两个的地方可使用这个方法代替,其中Lambda表达式的函数主体使用三木运算符。

default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)  

  “合并”,意味着旧值和新值都会参与计算并复制。给定key和value值参数,如果key值在Map中存在,则将旧value和给定的value一起计算出新value值作为key的值,如果新value为null,那么则从Map中删除key。如果key不存在,则将给定的value值直接作为key的值。

  Map映射集合类型作为Java中最重要以及最常用的数据结构之一,Map接口是它们的基类,在这个接口中定义了许多基础方法,而具体的实习则由它的子类完成。JDK8在Map接口中新值了许多default方法,这也为我们在实际编码中提供了很大的便利,如果是使用JDK8作为开发环境不妨多多学习使用新的API。

  

这是一个能给程序员加buff的公众号 

Java集合中的Map接口的更多相关文章

  1. Java集合中的Map接口怎么使用?

    Map(双列集合框架) 1.Map接口及实现类概述 Map 接口提供三种collection 视图,允许以键集.值集或键-值映射关系集的形式查看某个映射的内容.映射顺序 定义为迭代器在映射的 coll ...

  2. Java集合框架之Map接口浅析

    Java集合框架之Map接口浅析 一.Map接口综述: 1.1java.util.Map<k, v>简介 位于java.util包下的Map接口,是Java集合框架的重要成员,它是和Col ...

  3. Java中的集合(十三) 实现Map接口的Hashtable

    Java中的集合(十三) 实现Map接口的Hashtable 一.Hashtable简介 和HashMap一样,Hashtable采用“拉链法”实现一个哈希表,它存储的内容是键值对(key-value ...

  4. Java中的集合(十一) 实现Map接口的TreeMap

    Java中的集合(十一) 实现Map接口的TreeMap 一.TreeMap简介(基于JDK1.8) TreeMap是基于红黑树数据结构,是一个key-value的有序集合,该映射根据其键的自然顺序进 ...

  5. Java集合中List,Set以及Map等集合体系详解

    转载请注明出处:Java集合中List,Set以及Map等集合体系详解(史上最全) 概述: List , Set, Map都是接口,前两个继承至collection接口,Map为独立接口 Set下有H ...

  6. Java集合中List、Set以及Map

    概述: List , Set, Map都是接口:List , Set继承至Collection接口,Map为独立接口 Set下有HashSet,LinkedHashSet,TreeSet List下有 ...

  7. Java集合中Comparator和Comparable接口的使用

    在Java集合中,如果要比较引用类型泛型的List,我们使用Comparator和Comparable两个接口. Comparable接口 -- 默认比较规则,可比较的 实现该接口表示:这个类的实例可 ...

  8. 第19章 集合框架(3)-Map接口

    第19章 集合框架(3)-Map接口 1.Map接口概述 Map是一种映射关系,那么什么是映射关系呢? 映射的数学解释 设A,B是两个非空集合,如果存在一个法则,使得对A中的每一个元素a,按法则f,在 ...

  9. Java集合框架之Collection接口

    Java是一门面向对象的语言,那么我们写程序的时候最经常操作的便是对象了,为此,Java提供了一些专门用来处理对象的类库,这些类库的集合我们称之为集合框架.Java集合工具包位于Java.util包下 ...

随机推荐

  1. 解决eclipse出现This Android SDK requires Andro...date ADT to the latest version.问题

    更新完android SDK之后,eclipse出现了“This Android SDK requires Andro...date ADT to the latest version.”问题,这是因 ...

  2. 数据结构 链式哈希表(Hash Table)的接口定义与实现分析(完整代码)

    链式哈希表的接口定义 关于哈希表与链式哈希表的描述可以参阅:http://www.cnblogs.com/idreamo/p/7990860.html 链式哈希表的操作与属性有:初始化.销毁.插入元素 ...

  3. python_斐波那契数列

    什么是斐波那契数列? -- 一组第从第三个值开始,每个值都等于前两个值之和的一种有意思的数列 如[1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 如何用程序进行实现? -- 逻辑整 ...

  4. Maven的Archetype简介

    Archetype,骨架的意思. 文章出处:http://m.blog.csdn.net/blog/FireOfStar/42526027 Archetype是什么? 简单的说,Archetype是M ...

  5. junit参数化测试

    在前面的junit4初体验中我就说过,junit参数化测试是一只小怪兽,只逼编码痛点,现在我们这里来整理一下. 看过我前面的那篇初体验的就会发现一个问题,我们的测试代码大量的重复了.在这里先贴出原来的 ...

  6. maven系列--maven常用命令

    下一篇博客我会讲解用eclipse的m2插件来使用maven,这里先大概的了解下maven常用的命令.之后我在详细整理maven的生命周期,到时候会细致的讲解下这些指令应该要怎么使,maven都帮我们 ...

  7. trait

    参考 引文 在php中,为实现代码复用,有了继承,但是一个类只能继承一个父类,不支持多继承,接口支持多实现,但是接口又不太一样,接口对外负责功能调用声明,不负责实现,由实现了接口的类去实现具体功能逻辑 ...

  8. try{}catch(e){}不能捕获到异常

    只能捕获到ReferenceError异常,I don't know why. try{ aa();//这是一个未被定义的方法 }catch(e){ if(e instanceof Reference ...

  9. awk进阶整理

    BEGIN{写在前言,我英语不好,有许多地方直接使用的谷歌翻译.为了能理清awk工具使用的思路,详情还要看awk说明书(man awk) 或者http://www.gnu.org/software/g ...

  10. 【转】判断点在多边形内(matlab)

    inpolygon -Points inside polygonal region Syntax IN = inpolygon(X,Y,xv,yv)[IN ON] = inpolygon(X,Y,xv ...