java集合AbstractMap_Java 集合中的 AbstractMap 抽象类

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/394072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode392. 判断子序列(动态规划)

给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长&#xff08;长度 ~ 500,000&#xff09;&#xff0c;而 s 是个短字符串&#xff08;长度 <100&#xff09;。 字符串的一个子序列是原始字符串删…

让机器读懂用户——大数据中的用户画像

让机器读懂用户——大数据中的用户画像 摘要&#xff1a; 用户画像(persona)的概念最早由交互设计之父Alan Cooper提出:“Personas are a concrete representation of target users.” 是指真实用户的虚拟代表&#xff0c;是建立在一系列属性数据之上的目标用户模型。随着互联…

asp.net应用程序_如何在ASP.NET中为聊天应用程序构建键入指示器

asp.net应用程序by Neo Ighodaro由新Ighodaro 如何在ASP.NET中为聊天应用程序构建键入指示器 (How to build a typing indicator for your chat app in ASP.NET) A basic understanding of ASP.NET and jQuery is needed to follow this tutorial.要学习本教程&#xff0c;需要…

activeMQ在文件上传的应用

本次试验主要用到了activeMq和上传插件uploadify的知识&#xff0c;感谢以下两篇文章的作者。 1.http://itindex.net/detail/47160-java-jquery-%E4%B8%8A%E4%BC%A0 2.http://blog.csdn.net/jiuqiyuliang/article/details/47160259 本文中不再提供activeMq和uploadify的介绍。 …

java nginx 例子_Java及nginx实现文件权限控制代码实例

我们知道&#xff0c;使用nginx作为文件下载服务器&#xff0c;可以极大地降低对后端Java服务器的负载冲击&#xff0c;但是nginx本身并不提供授权控制&#xff0c;因此好的方案是由后端服务器实现权限控制&#xff0c;最好的方式是直接复用应用的认证体系&#xff0c;最大化的…

leetcode934. 最短的桥(dfs+bfs)

在给定的二维二进制数组 A 中&#xff0c;存在两座岛。&#xff08;岛是由四面相连的 1 形成的一个最大组。&#xff09; 现在&#xff0c;我们可以将 0 变为 1&#xff0c;以使两座岛连接起来&#xff0c;变成一座岛。 返回必须翻转的 0 的最小数目。&#xff08;可以保证答…

谢烟客---------Linux之DNS服务系统的基础知识

DNS Domain Name Server1)C/S架构&#xff1a;SOCKET通信IP PORT2&#xff09;应用层协议&#xff1a;资源子网BIND Berkerley Information Name DomainDNS由来1&#xff09;统一名字&#xff0c;自己维护 <自己查询>解析: 基于key查找value: 查询数据库(二维关系的表: …

Java实现点击导出excel页面遮罩屏蔽,下载完成后解除遮罩

一、问题场景 最近在做数据统计功能&#xff0c;需求是导出大数据量的excel&#xff0c;时间间隔较长&#xff0c;大概需要十秒左右&#xff0c;点击导出后&#xff0c;页面没有做任何处理&#xff0c;用户也不知道是否正在导出&#xff1b;如果没有做交互上的限制&#xff0c;…

pbs 支持 java_Linux下Java安装与配置

安装以JDK1.6.0_43为例下载jdk-6u43-linux-x64.bin&#xff0c;http://www.oracle.com/technetwork/java/javase/downloads/index.html增加可执行权限 chmod x jdk-6u43-linux-x64.bin&#xff0c;执行 ./jdk-6u43-linux-x64.bin 生成目录jdk1.6.0_43拷贝到/usr/share下&#x…

使用Chatkit构建Node.js命令行聊天应用程序

by Hugo雨果 使用Chatkit构建Node.js命令行聊天应用程序 (Build a Node.js command-line chat application with Chatkit) Building chat in your app can be pretty complex. Yet, with Chatkit, implementing fully-featured chat is nothing but a few lines of code.在您的…

java 毫秒转分钟和秒_Java程序将毫秒转换为分钟和秒

Java程序将毫秒转换为分钟和秒在上面的程序中&#xff0c;您将学习如何在Java中将毫秒分别转换为分钟和秒。示例1&#xff1a;将毫秒分别转换为分钟和秒import java.util.concurrent.TimeUnit;public class Milliseconds {public static void main(String[] args) {long millis…

Andrew Ng机器学习之一 导论

监督学习与无监督学习 监督学习&#xff08;Supervised Learning) Ng的原文是&#xff1a; We gave the algorithm a data set that the "right answers" were given. 即给定了一个正确结果的集合供算法学习&#xff0c;强调了需要实现准备好正负样本喂给机器。 无监…

leetcode994. 腐烂的橘子(bfs)

在给定的网格中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b; 值 1 代表新鲜橘子&#xff1b; 值 2 代表腐烂的橘子。 每分钟&#xff0c;任何与腐烂的橘子&#xff08;在 4 个正方向上&#xff09;相邻的新鲜橘子都会腐烂。 返回直…

ES6对象的扩展

1.属性简写表示 2.方法简写表示 属性与方法简写&#xff1a; 3.属性名表达式 ES6允许字面量定义对象时&#xff0c;用方法二&#xff08;表达式&#xff09;作为对象的属性名&#xff0c;即把表达式放在方括号内。 4.Object.is()比较两个值是否严格相等 转载于:https://www.cnb…

Spring Cloud项目MVN编译 -- Non-resolvable import POM

最近利用闲余时间&#xff0c;打算搭建一套基于Spring Cloud G版的微服务架构(Spring boot 2.1.0)&#xff0c;一顿操作之后,IDEA也没有提示什么错误,自认为微服务搭建完毕。启动项目前&#xff0c;习惯性的Maven -clean了一下&#xff0c;我去&#xff0c;IDEA里面的Maven Pro…

datax底层原理_Datax 插件加载原理

Datax 插件加载原理插件类型Datax有好几种类型的插件&#xff0c;每个插件都有不同的作用。reader&#xff0c; 读插件。Reader就是属于这种类型的writer&#xff0c; 写插件。Writer就是属于这种类型的transformer&#xff0c; 目前还未知handler&#xff0c; 主要用于任务执行…

mysql windows身份验证_SQL Server 2005 怎么就不能用Windows身份验证方式登录呢?

SQL Server 2005 自从装到我的电脑上始终无法使用Windows身份验证的方式登录,由于使用用户名和密码登录还算顺畅,所以一直忽略了这SQL Server 2005 自从装到我的电脑上始终无法使用Windows身份验证的方式登录,由于使用用户名和密码登录还算顺畅,所以一直忽略了这个问题,直到又有…

JavaScript正则表达式快速简单的指南

Interested in learning JavaScript? Get my ebook at jshandbook.com有兴趣学习JavaScript吗&#xff1f; 在jshandbook.com上获取我的电子书 正则表达式简介 (Introduction to Regular Expressions) A regular expression (also called regex for short) is a fast way to w…

leetcode104. 二叉树的最大深度(dfs)

给定一个二叉树&#xff0c;找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。示例&#xff1a; 给定二叉树 [3,9,20,null,null,15,7]&#xff0c;3/ \9 20/ \15 7 返回它的最大深度 3 。代码 class Soluti…

[解读REST] 3.基于网络应用的架构

链接上文[解读REST] 2.REST用来干什么的&#xff1f;&#xff0c;上文中解释到什么是架构风格和应该以怎样的视角来理解REST&#xff08;Web的架构风格&#xff09;。本篇来介绍一组自洽的术语&#xff0c;用它来描述和解释软件架构&#xff1b;以及列举下对于基于网络的应用来…