Java集合中的Map接口

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是如何排序的:

 1 /**
 2  * Sort a Map by Keys.——JDK7
 3  * @param map To be sorted Map.
 4  * @return Sorted Map.
 5  */
 6 public Map<String, Integer> sortedByKeys(Map<String, Integer> map) {
 7     List<Map.Entry<String, Integer>> list = new LinkedList<>(map.entrySet());
 8     Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
 9         @Override
10         public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
11             return o1.getKey().compareTo(o2.getKey());
12         }
13     });
14     Map<String, Integer> linkedMap = new LinkedHashMap<>();
15     Iterator<Map.Entry<Strin    g, Integer>> iterator = list.iterator();
16     while (iterator.hasNext()) {
17         Map.Entry<String, Integer> entry = iterator.next();
18         linkedMap.put(entry.getKey(), entry.getValue());
19     }
20
21     return linkedMap;
22 }

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

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

  可见代码量大大减少,简而言之,这四个方法是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之前存在以下代码:

1 HashMap hashMap = new HashMap();
2 ArrayList arrayList = new ArrayList();
3 hashMap.put(arrayList, "this is list");
4 System.out.println(hashMap.get(arrayList));
5 LinkedList linkedList = new LinkedList();
6 System.out.println(hashMap.get(linkedList));

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

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

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

  上面这种情况把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对象引用相关的一些知识。

1 Map<String, Integer> map = new HashMap<String, Integer>();
2 map.put("a", 1);
3 map.put("b", 2);
4 System.out.println(map.keySet());        //output: [a, b]
5 Set<String> sets = map.keySet();
6 sets.remove("a");
7 System.out.println(map.keySet());        //output: [b]
8 sets.add("c");        //output: throws UnsupportedOperationException
9 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表达式作为参数,例如:

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

default V putIfAbsent(K key, V value)

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

1 if (!map.containsKey(key, value)) {
2     map.put(key, value);
3 } else {
4     map.get(key);
5 }

  在之前提到了一个方法和这个类似——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实现本地缓存中随处可见,这个方法类似于下列代码:

1 if (map.get(key) == null) {
2     value = func(key);      //计算value值
3     map.put(key, value);
4 }
5 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的公众号 

原文地址:https://www.cnblogs.com/yulinfeng/p/8476573.html

时间: 2024-08-02 22:28:55

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

JavaSE入门学习37:Java集合框架之Map接口及其实现类HashMap和TreeMap

一Map接口 Map接口中的每个成员方法由一个关键字(key)和一个值(value)构成.Map接口不直接继承于Collection接口,因 为它包装的是一组成对的"键-值"对象的集合,而且在Map接口的集合中也不能有重复的key出现,因为每个键只能与 一个成员元素相对应. Map接口定义了存储"键(key)--值(value)映射对"的方法.实现Map接口的类用来存储键值对.Map接口中包含 了一个keySet()方法,用于返回Map中所有key组成的Set集合.

java中的Map接口

前言 在学习Map接口之前,先来看看散列表的概念:本节主要讲讲Map接口的实现类和集合的工具类. 这里先提一下泛型编程:通常情况下用来限制集合的存储的数据类型.在定义集合时使用<引用类型>来限制集合.其中<>的类型只为引用类型. 正文 散列桶 1.散列桶的概念 散列桶的目的是使用空间换时间,即它是使用索引来提高效率. 散列桶把一组数据通过散列算法,将数据分散开来:存储于连续的内存空间中(数组),将散列值作为数组的下标:如果散列值有重复的,将重复的数据放置在散列桶中.散列桶是线性表.

Java 集合系列 08 Map架构

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 Java 集合系列 05 Vector详细介绍(源码解析)和使用示例 Java 集合系列 06 Stack详细介绍(源码解析)和使用示例 Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和

Java集合框架之List接口

在上一篇Java集合框架之Collection接口中我们知道List接口是Collection接口的子接口,List接口对Collection进行了简单的扩充,List接口中的元素的特点为有序,可重复,允许null值,因为List继承了Collection接口,所以继承自Collection接口中的方法不再赘述,从List接口中的方法来看,List接口主要是增加了面向位置的操作,允许在指定位置上对集合中的元素进行操作,同时增加了一个能够双向遍历线性表的新列表迭代器ListIterator.下面介

Java集合中的LinkedHashMap类

jdk1.8.0_144 本文阅读最好先了解HashMap底层,可前往<Java集合中的HashMap类>. LinkedHashMap由于它的插入有序特性,也是一种比较常用的Map集合.它继承了HashMap,很多方法都直接复用了父类HashMap的方法.本文将探讨LinkedHashMap的内部实现,以及它是如何保证插入元素是按插入顺序排序的. 在分析前可以先思考下,既然是按照插入顺序,并且以Linked-开头,就很有可能是链表实现.如果纯粹以链表实现,也不是不可以,LinkedHashM

java集合中List与set的区别

java集合中List与set的区别.     List可以存储元素为有序性并且元素可以相同.     set存储元素为无序性并且元素不可以相同.     下面贴几段代码感受一下: ArrayList list = new ArrayList();//构造出List对象 list.add(1); list.add("string"); list.add(true); list.add(3.14); list.add(null); for(int i = 0; i < size()

Java 集合系列 15 Map总结

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 Java 集合系列 05 Vector详细介绍(源码解析)和使用示例 Java 集合系列 06 Stack详细介绍(源码解析)和使用示例 Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和

Java集合中Map接口的使用方法

Map接口 Map提供了一种映射关系,其中的元素是以键值对(key-value)的形式存储的,能够实现根据key快速查找value: Map中的键值对以Entry类型的对象实例形式存在: 建(key值)不可重复,value值可以重复,一个value值可以和很多key值形成对应关系,每个建最多只能映射到一个值. Map支持泛型,形式如:Map<K,V> Map中使用put(K key,V value)方法添加 HashMap类 HashMap是Map的一个重要实现类,也是最常用的,基于哈希表实现

Java的LinkedHashSet、Map接口、可变参数、集合嵌套、

1.LinkedHashSet:(1)LinkedHashSet集合保证元素的存入和取出的顺序: package com.oracle.demo01; import java.util.HashSet; import java.util.LinkedHashSet; public class demo01 { public static void main(String[] args) { //不能存重复元素,但是LinkedHashSet是有序的. LinkedHashSet<String>