Java 集合中的 AbstractMap 抽象类

jdk1.8.0_144

AbstractMap 抽象类实现了一些简单且通用的方法, 本身并不难但在这个抽象类中有两个方法非常值得关注, keySet 和 values 方法源码的实现可以说是教科书式的典范

抽象类通常作为一种骨架实现, 为各自子类实现公共的方法上一篇我们讲解了 Map 接口, 此篇对 AbstractMap 抽象类进行剖析研究

Java 中 Map 类型的数据结构有相当多, AbstractMap 作为它们的骨架实现实现了 Map 接口部分方法, 也就是说为它的子类各种 Map 提供了公共的方法, 没有实现的方法各种 Map 可能有所不同

抽象类不能通过 new 关键字直接创建抽象类的实例, 但它可以有构造方法 AbstractMap 提供了一个 protected 修饰的无参构造方法, 意味着只有它的子类才能访问 (当然它本身就是一个抽象类, 其他类也不能直接对其实例化), 也就是说只有它的子类才能调用这个无参的构造方法

在 Map 接口中其内部定义了一个 Entry 接口, 这个接口是 Map 映射的内部实现用于维护一个 key-value 键值对, key-value 存储在这个 Map.Entry 中 AbstractMap 对这个内部接口进行了实现, 一共有两个: 一个是可变的 SimpleEntry 和一个是不可变的 SimpleImmutableEntry

public static class SimpleEntry implements Entry, java.io.Serializable

实现了 Map.Entry 接口, 并且实现了 Serializable(可被序列化)

它的方法比较简单都是取值存值的操作, 对于 key 值的定义是一个 final 修饰意味着是一个不可变的引用另外其 setValue 方法稍微特殊, 存入 value 值返回的并不是存入的值, 而是返回的以前的旧值需要重点学习的是它重写的 equals 和 hashCode 方法publicbooleanequals(Objecto){

if(!(oinstanceofMap.Entry))// 判断参数是否是 Map.Entry 类型, 要 equals 相等首先得是同一个类型

returnfalse;

Map.Entry,?>e=(Map.Entry,?>)o;// 将 Object 类型强转为 Map.Entry 类型, 这里参数使用? 而不是 K, V 是因为泛型在运行时类型会被擦除, 编译器不知道具体的 K,V 是什么类型

returneq(key,e.getKey())&&eq(value,e.getValue());//key 和 value 分别调用 eq 方法进行判断, 都返回 ture 时 equals 才相等

}

privatestaticbooleaneq(Objecto1,Objecto2){

returno1==null?o2==null:o1.equals(o2);// 这个三目运算符也很简单, 只不过需要注意的是尽管这里 o1o2 是 Object 类型, Object 类型的 equals 方法是通过 == 比较的引用, 所以不要认为这里有问题, 因为在实际中, o1 类型有可能是 String, 尽管被转为了 Object, 所以此时在调用 equals 方法时还是调用的 String#equals 方法

}

要想正确重写 equals 方法并能正确使用, 通常还需要重写 hashCode 方法publicinthashCode(){

return(key==null?0:key.hashCode())^(value==null?0:value.hashCode());//key 和 value 的值不为 null 时, 将它们的 hashCode 进行异或运算

}

publicstaticclassSimpleImmutableEntryimplementsEntry,java.io.SerializableSimpleImmutableEntry

定义为不可变的 Entry, 其实是事实不可变, 因为它不提供 setValue 方法, 在多个线程同时访问时自然不能通过 setValue 方法进行修改它相比于 SimpleEntry 其 key 和 value 成员变量都被定义为了 final 类型调用 setValue 方法将会抛出 UnsupportedOperationException 异常

它的 equals 和 hashCode 方法和 SimpleEntry 一致

接下来查看 AbstractMap 抽象类实现了哪些 Map 接口中的方法

public int size()

Map 中定义了一个 entrySet 方法, 返回的是 Map.Entry 的 Set 集合, 直接调用 Set 集合的 size 方法即是 Map 的大小

public boolean isEmpty()

调用上面的 size 方法, 等于 0 即为空

public boolean containsKey(Object key)

这个方法的实现较为简单, 通过调用 entrySet 方法获取 Set 集合的迭代器遍历 Map.Entry, 与参数 key 比较 Map 可以存储为 null 的 key 值, 由于 key=null 在 Map 中存储比较特殊 (不能计算 hashCode 值), 所以在这里也做了判断参数 key 是否为空

public boolean containsValue(Object value)

这个方法实现和 containsKey 一致

public V get(Object key)

这个方法实现和上面两个也类似, 不同的是上面相等返回 boolean, 这个方法返回 value 值

public V put(K key, V value)

向 Map 中存入 key-value 键值对的方法并没有具体实现, 会直接抛出一个 UnsupportedOperationException 异常

public V remove(Object key)

通过参数 key 删除 Map 中指定的 key-value 键值对这个方法也很简单, 也是通过迭代器遍历 Map.Entry 的 Set 集合, 找到对应 key 值, 通过调用 Iterator#remove 方法删除 Map.Entry

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

这个方法也很简单遍历传入的 Map, 调用 put 方法存入就可以了

public void clear()

调用 entrySet 方法获取 Set 集合再调用 Set#clear() 方法清空

public Set keySet()

返回 Map key 值的 Set 集合 AbstractMap 中定义了一个成员变量 transient Set keySet, 在 JDK7 中 keySet 变量是由 volatile 修饰的, 但在 JDK8 中并没有使用 volatile 修饰在对 keySet 变量的注释中解释道, 访问这些字段的方法本身就没有同步, 加上 volatile 也不能保证线程安全关于 keySet 方法的实现就有点意思了

首先思考该方法是返回 key 值的 Set 集合, 很自然的能想到一个简单的实现方式, 遍历 Entry 数组取出 key 值放到 Set 集合中, 类似下面代码:publicSetkeySet(){

Setks=null;

for(Map.Entryentry:entrySet()){

ks.add(entry.getKey());

}

returnks;

}

这就意味着每次调用 keySet 方法都会遍历 Entry 数组, 数据量大时效率会大大降低不得不说 JDK 源码是写得非常好, 它并没有采取遍历的方式如果不遍历 Entry, 那又如何知道此时 Map 新增了一个 key-value 键值对呢?

答案就是在 keySet 方法内部重新实现了一个新的自定义 Set 集合, 在这个自定义 Set 集合中又重写了 iterator 方法, 这里是关键, iterator 方法返回 Iterator 接口, 而在这里又重新实现了 Iterator 迭代器, 通过调用 entrySet 方法再调用它的 iterator 方法下面结合代码来分析:publicSetkeySet(){

Setks=keySet;// 定义的 transient Set keySet

if(ks==null){// 第一次调用肯定为 null, 则通过下面代码创建一个 Set 示例

ks=newAbstractSet(){// 创建一个自定义 Set

publicIteratoriterator(){// 重写 Set 集合的 iterator 方法

returnnewIterator(){// 重新实现 Iterator 接口

privateIterator

V>>i=entrySet().iterator();// 引用 Entry 的 Set 集合 Iterator 迭代器

publicbooleanhasNext(){

returni.hasNext();// 对 key 值的判断, 就是对 entry 的判断

}

publicKnext(){

returni.next().getKey();// 取下一个 key 值, 就是取 entry#getKey

}

publicvoidremove(){

i.remove();// 删除 key 值, 就是删除 entry

}

};

}

publicintsize(){// 重写的 Set#size 方法

returnAbstractMap.this.size();//key 值有多少就是整个 Map 有多大, 所以调用本类的 size 方法即可这个是内部类, 直接使用 this 关键字代表这个类, 应该指明是调用 AbstractMap 中的 size 方法, 没有 this 则表示是 static 静态方法

}

publicbooleanisEmpty(){// 重写的 Set#isEmpty 方法

returnAbstractMap.this.isEmpty();// 对是否有 key 值, 就是判断 Map 是否为空,, 所以调用本类的 isEmpty 方法即可

}

publicvoidclear(){// 重写的 Set#clear 方法

AbstractMap.this.clear();// 清空 key 值, 就是清空 Map,, 所以调用本类的 clear 方法即可

}

publicbooleancontains(Objectk){// 重写 Set#contains 方法

returnAbstractMap.this.containsKey(k);// 判断 Set 是否包含数据 k, 就是判断 Map 中是否包含 key 值, 所以调用本类的 containsKey 方法即可

}

};

keySet=ks;// 将这个自定义 Set 集合赋值给变量 keySet, 在以后再次调用 keySet 方法时, 因为 keySet 不为 null, 只需直接返回

}

returnks;

我认为这是一种很巧妙的实现, 尽管这个方法是围绕 key 值, 但实际上可以结合 Entry 来实现, 而不用遍历 Entry, 同时上面提到了调用 entrySet# iterator 方法, 这里则又是模板方法模式的最佳实践因为 entrySet 在 AbstractMap 中并未实现, 而是交给了它的子类去完成, 但是对于 keySet 方法却可以对它进行一个算法骨架 实现, 这就是模板方法模式

public Collection values()

对于 values 方法则完全可以参考 keySet, 两者有着异曲同工之妙, 这里为节省篇幅不再赘述

public abstract Set> entrySet()

一个抽象方法, 交给它的子类去完成, 说明这个方法并不是特别通用

public boolean equals(Object o)

Map 中规定只有在 Map 中的每对 key-value 键值对的 key 和 value 都一一对应时他们的 equals 比较才返回 true 在方法中先判断简单的条件, 如果引用相等, 直接返回 true, 如果参数 o 不是 Map 类型直接返回 false, 如果两个 Map 的数量不同也直接返回 false 后面才再遍历 Entry 数组比较 Entry 中的 key 和 value 是否一一对应方法简单, 但这给了我们一个启示, 在条件判断中, 先判断简单的基本的, 再判断复杂的

public int hashCode()

重写了 Object 类的 equals 方法, 重写 hashCode 也是必须的 AbstractMap 对 hashCode 的实现是将所有 Map.Entry(这里就是 SimpleEntry 或 SimpleImmutableEntry) 的 hashCode 值向加, 最后得出的总和作为 Map 的 hashCode 值

public String toString()

这个方法没什么好说的, 就是取出所有键值对使用 StringBuilder 对其进行拼接

protected Object clone() throws CloneNotSupportedException

实现一个浅拷贝, 由于是浅拷贝对于变量 keySet 和 values 不进行拷贝, 防止两个浅拷贝引发的问题, 关于 Object 中的 clone 方法在万类之父 Object 已有解析

来源: https://www.cnblogs.com/yulinfeng/p/8486539.html

java集合AbstractMap_Java 集合中的 AbstractMap 抽象类相关推荐

  1. Java有关于面向对象中的【抽象类、抽象方法和多态】的解释(初学者)

    这里写自定义目录标题 前言 Java中的抽象 基本类 && 基本方法 抽象类 && 抽象方法 抽象类的公有抽象方法 子类的实现父类公有方法 多个子类中与众不同的私有方法 ...

  2. java collectiongroup 类_Java中的collection集合类型总结

    java集合是java提供的工具包,包含了常用的数据结构:集合.链表.队列.栈.数组.映射等.java集合工具包位置是java.util.* java集合主要可以划分为4个部分:list列表.set集 ...

  3. java集合总结_Java中集合总结

    Java数组的长度是固定的,为了使程序能够方便地存储和操作数目不固定的一组数据,JDK类库提供了Java集合,这些集合类都位于java.util包中,但是与数组不同的是,集合中不能存放基本类型数据,而 ...

  4. 浅入深出之Java集合框架(中)

    Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到浅入深出之Java集合框架(下). ...

  5. java 读取集合到流中_Java 10:将流收集到不可修改的集合中

    java 读取集合到流中 Java 10引入了几种新方法来促进不可修改集合的创建. List.copyOf , Set.copyOf和Map.copyOf方法从现有实例创建新的集合实例. 例如: Li ...

  6. java中的集合框架_JAVA中的集合框架(上)List

    第一节 JAVA中的集合框架概述 集合的概念,现实生活中:很多事物凑在一起就是一个集合:数学中的集合:具有相同属性事物的总体:JAVA中的集合:是一种工具类,就像是容器,储存任意数量的具有共同属性的对 ...

  7. Java集合Collection接口中的常用方法演示

    Java集合Collection接口中的常用方法演示 添加 add(Objec tobj) 和 addAll(Collection coll) 获取有效元素的个数 int size() 清空集合 vo ...

  8. Java集合或Map中元素排序及过滤

    在Java中,对集合或Map中元素进行排序或过滤是一个频繁操作.这里以List为例介绍下如何在集合中实现元素的排序和过滤功能.对于非List元素(Set.Map)等,一方面可以参考List使用类似的方 ...

  9. java map 实例_java中map集合嵌套形式简单示例

    定义了一个学生类,封装了id和name属性,提供一个全参构造器,并复写toSting方法 class Student{ private String id; private String name; ...

最新文章

  1. 拼音开头有什么字_excel查找函数应用:如何提取姓名的拼音首字母
  2. python爬虫教程视频-13天搞定Python分布爬虫
  3. Windows7 WIN 7 64位 环境编译6sv2.1版本的大气传输模型
  4. 实战CentOS系统部署Hadoop集群服务
  5. SQL日期时间和字符串函数
  6. 公差基本偏差代号_螺纹基础知识学习,螺纹公差标准的结构,螺纹公差带与旋合长度...
  7. SpringBoot2.1.9 Mybatis由于@Mapper注解多数据源配置不生效问题
  8. 计算机中2的四次方为啥是4位,计算机基础试题2(4页)-原创力文档
  9. Go面试题 | []int 能转换为 []interface 吗?
  10. cadence 常见pcb电阻_高速PCB培训手记
  11. Codeforces 1163A - Eating Soup
  12. 微软Power BI 每月功能更新系列——3月Power BI 新功能学习
  13. Java annotation 自定义注释@interface的用法 转载记录
  14. mysql 隐秘后门_Phpstudy被暴存在隐藏后门-检查方法
  15. 强烈推荐大家看这篇文章:iOS开发常用三方库、插件、知名博客等等(特别有用)
  16. 坑爹的360漏洞修补造成win7黑屏
  17. html5微信分享图片不显示,微信分享ios 不显示图片和简介问题总结
  18. 002--软考程序员之硬件组成原理
  19. Element-UI中打开本地文件
  20. 微信小程序实现分页加载,触底加载下一页,滚动加载

热门文章

  1. PowerShell入门(三):如何快速地掌握PowerShell?
  2. hive 集成sentry
  3. 汉能:让人类像叶绿素一样利用太阳能
  4. 构建微服务:Spring boot 入门篇
  5. 软件工程----9软件实现
  6. hive实例,GPRS流量统计
  7. 高扫后督解决方案 力助银行内部核查
  8. JavaScript窗体控制函数
  9. 计算机应用基础成教作业,(计算机应用基础成教08A卷1.doc
  10. MySQL set names 命令_mysql set names 命令和 mysql 字符编码问题