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

(A)SimpleEntry

  public static class SimpleEntry<K,V> implements Entry<K,V>, java.io.Serializable

它的方法比较简单都是取值存值的操作,对于key值的定义是一个final修饰意味着是一个不可变的引用。另外其setValue方法稍微特殊,存入value值返回的并不是存入的值,而是返回的以前的旧值

private final K key;
private V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
....

需要重点学习的是它重写的equals和hashCode方法。

public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) //判断参数是否是Map.Entry类型,要equals相等首先得是同一个类型
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o; //将Object类型强转为Map.Entry类型,这里参数使用“?”而不是“K, V”是因为泛型在运行时类型会被擦除,
                          //编译器不知道具体的K,V是什么类型
return eq(key, e.getKey()) && eq(value, e.getValue()); //key和value分别调用eq方法(内置)进行判断,都返回ture时equals才相等。
}
//内置的eq方法
private static boolean eq(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
//这个三目运算符也很简单,只不过需要注意的是尽管这里o1、o2是Object类型,Object类型的equals方法是通过“==”比较的引用,
  //所以不要认为这里有问题,因为在实际中,o1类型有可能是String,尽管被转为了Object,所以此时在调用equals方法时还是调用的String#equals方法。
}
public int hashCode() {
//key和value的值不为null时,将它们的hashCode进行异或运算。
return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
}

(B)SimpleImmutableEntry

  public static class SimpleImmutableEntry<K,V> implements Entry<K,V>, java.io.Serializable

  定义为不可变Entry,其实是事实不可变,因为它不提供setValue方法,在多个线程同时访问时自然不能通过setValue方法进行修改

  它相比于SimpleEntrykeyvalue成员变量都被定义为了final类型。调用setValue方法将会抛出UnsupportedOperationException异常。

    它的equals和hashCode方法和SimpleEntry一致。

private final K key; //属性都设为了final
private final V value;
//有获取k和value的方法
public K getKey() { return key; }
public V getValue() { return value; }
//没有set方法,调用此方法时,抛出异常
public V setValue(V value) { throw new UnsupportedOperationException();}

AbstractMap抽象类方法实现

======Query Operations查询操作,获取大小,是否为空,是否存在,获取数据等内容

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值。

===========Modification Operations修改操作,添加、删除等内容

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。

=========== Bulk Operations批量操作,批量删除,整体清空

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

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

public void clear()

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


我们主要来看看keySet和values方法。

// 源码结构
transient Set<K> keySet; //被transient关键字修饰,表示变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。如英文单词的意思:短暂的|瞬时的
transient Collection<V> values;
public Set<K> keySet(){...} //返回Map key值的Set集合
public Collection<V> values(){...} //返回Map value值的Collection集合

public Set<K> keySet()

  返回key值的Set集合,很自然的能想到一个简单的实现方式,遍历Entry数组取出key值放到Set集合中, 这就意味着每次调用keySet方法都会遍历Entry数组,数据量大时效率会大大降低。

  而JDK源码怎么解决的呢?keySet方法内部重新实现了一个新的自定义Set集合,在这个自定义Set集合中又重写iterator方法这里是关键iterator方法返回Iterator接口,而在这里又重新实现Iterator迭代器,通过调用entrySet方法再调用它的iterator方法。下面结合代码来分析:

public Set<K> keySet() {
Set<K> ks = keySet; //定义的transient Set<K> keySet
if (ks == null) { //第一次调用肯定为null,则通过下面代码创建一个Set示例
ks = new AbstractSet<K>() { //创建一个自定义Set
public Iterator<K> iterator() { //重写Set集合的iterator方法
return new Iterator<K>() { //重新实现Iterator接口
private Iterator<Entry<K,V>> i = entrySet().iterator(); //引用Entry的Set集合Iterator迭代器
public boolean hasNext() {
return i.hasNext(); //对key值的判断,就是对entry的判断
}
public K next() {
return i.next().getKey(); //取下一个key值,就是取entry#getKey
}
public void remove() {
i.remove(); //删除key值,就是删除entry
}
};
}
public int size() { //重写的Set#size方法
return AbstractMap.this.size(); //key值有多少就是整个Map有多大,所以调用本类的size方法即可。
                               //这个是内部类,直接使用this关键字代表这个类,应该指明是调用AbstractMap中的size方法,没有this则表示是static静态方法
} public boolean isEmpty() { //重写的Set#isEmpty方法
return AbstractMap.this.isEmpty(); //对是否有key值,就是判断Map是否为空,,所以调用本类的isEmpty方法即可
} public void clear() { //重写的Set#clear方法
AbstractMap.this.clear(); //清空key值,就是清空Map,,所以调用本类的clear方法即可
} public boolean contains(Object k) { //重写Set#contains方法
return AbstractMap.this.containsKey(k); //判断Set是否包含数据k,就是判断Map中是否包含key值,所以调用本类的containsKey方法即可
}
};
keySet = ks; //将这个自定义Set集合赋值给变量keySet,在以后再次调用keySet方法时,因为keySet不为null,只需直接返回。
}
return ks;

  尽管这个方法是围绕key值,但实际上可以结合Entry来实现,而不用遍历Entry,同时上面提到了调用entrySet# iterator方法,这里则又是模板方法模式最佳实践

  因为entrySet在AbstractMap中并未实现,而是交给了它的子类去完成,但是对于keySet方法却可以对它进行一个“算法骨架” 实现,这就是模板方法模式

public Collection<V> values()

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

public abstract Set<Entry<K,V>> entrySet()

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

public boolean equals(Object o)

  Map中规定只有在Map中的每对key-value键值对keyvalue都 一 一对应时他们的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

最新文章

  1. Analysis Services OLAP 概述
  2. redis:hash 数据类型
  3. [WinAPI] API 5 [遍历驱动器并获取驱动器属性]
  4. Intellij IDEA开发第一个Android应用
  5. 笔试之Linux命令的使用
  6. kafka学习(四)-Topic &amp; Partition
  7. 安装ecshop的问题处理
  8. 错误:Warning: Attempt to present &lt;UIAlertController: 0x7fd192806e20&gt; on &lt;ViewController: 0x7fd1928048d0&gt; whose view is not in the window hierarchy!
  9. 浅谈JavaWeb架构演变
  10. git在开发中的一些使用
  11. mysql 开发基础系列18 存储过程和函数(下)
  12. tp5的路由
  13. IMAP 读取含有附件邮件超慢问题
  14. sklearn.preprocessing.LabelBinarizer
  15. 使用maven构建多模块项目,分块开发
  16. gooreplacer 很好用
  17. 【HDOJ1217】【Floyd求最长路】
  18. 3130: [Sdoi2013]费用流
  19. C#调用外部DLL介绍及使用详解
  20. 基于Cocos2d-x学习OpenGL ES 2.0系列——你的第一个三角形(1)

热门文章

  1. 第二次作业社团UML图
  2. .net core 资料网站 和 开源项目
  3. insomnihack CTF 2016-microwave
  4. java集合类型源码解析之PriorityQueue
  5. DELPHI解析JSON格式化的日期
  6. win10 合并磁盘 disk Acronis Disk Director
  7. NLP 文本预处理
  8. C# 将文本写入到文件
  9. VMware与宿主机同一网段
  10. 【集成模型】Stacking