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. 多IP服务器应用可以有效的降低成本

    多IP的常规应用很多,SEO,EDM,VPN代理等.可以有效的解决成本,很多时候的租用一台高配置服务器通过XEN,hyper-V等虚拟化技术分割成VPS ,共用一台服务器就会大大的降低成本,这样就需要 ...

  2. linux下安装python3

    不建议卸载python2 可能会导致系统内其他软件无法使用 1.下载 wget https://www.python.org/ftp/python/3.6.0/Python-3.6.0a1.tar.x ...

  3. git log 查看指定文件的提交记录

    使用git log除了可以看整个仓库的提交记录外,还可以指定某个文件的提交记录. 1. 查看指定文件的历史提交记录 命令: git log -- <file> 说明:只需要指定文件名称. ...

  4. linux_nginx环境配置

    rpm -ivh https://mirrors.aliyun.com/epel/epel-release-latest-6.noarch.rpm # 安装阿里的epel源 国内使用web站点最多的w ...

  5. java里程碑之泛型--深入理解泛型

    所谓泛型,就是允许在定义类,接口,方法时使用类型形参,这个类型形参将在声明变量,创建对象,调用方法的时候动态的指定.JAVA5之后修改了集合中所有的接口和类,为这些接口和类都提供了泛型的支持. 关于泛 ...

  6. Tomcat服务器的Web安全的解决方法

    .概述 在任何一种WEB应用开发中,不论大中小规模的,每个开发者都会遇到一些需要保护程序数据的问题,涉及到用户的LOGIN ID和PASSWORD.那么如何执行验证方式更好呢?实际上,有很多方式来实现 ...

  7. 如何为form表单的button设置submit事件

    若button按钮没有type属性,浏览器默认按照type=submit逻辑处理,这样若将没有type的button放在form表单中,点击按钮就会执行form表单提交

  8. <select>设置multiple="multiple"属性后 下拉框全部展开了 不再是折叠的怎么回事

    1 <select multiple="multiple" size="5"> <option>1</option> < ...

  9. awk进阶整理

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

  10. 关于MyEclipse启动报错:Error starting static Resources;下面伴随Failed to start component [StandardServer[8005]]; A child container failed during start.的错误提示解决办法.

    最后才发现原因是Tomcat的server.xml配置文件有问题:apache-tomcat-7.0.67\conf的service.xml下边多了类似与 <Host appBase=" ...