上一篇写了Map接口的源码分析,这一篇写一下Map接口的一个实现类AbstractMap,从名字就可以看出这是一个抽象类,提供了Map接口的骨架实现,为我们实现Map接口的时候提供了很大的便利。在这里类中,还有一个抽象方法entrySet没有被实现,在实现的方法中put方法也仅仅抛出了一个异常。我们在继承这个类写自己的Map时,如果是一个不支持赋值的Map,那么只需要实现entrySet方法。如果是实现一个可以添加键值对的Map,那么不仅要实现entrySet方法,还需要在entrySet返回的那个迭代器中的remove方法。

本篇会参考http://blog.csdn.net/Airsaid/article/details/51178444写的分析的组织方式,并最后简单的实现一个Map作为实例

源码分析

构造方法

 protected AbstractMap() {
}

在Map的建议中,是希望有两个构造函数,一个是无参构造函数,另一个是以Map为参数的构造函数,但此处只实现了一个无参构造函数,给子类提供了一个构造方法

成员变量

 transient Set<K>        keySet;
transient Collection<V> values;

这两个成员将分别保存Map当中的键和值,因为Map要保证键的唯一性,所以在保存键的时候使用了Set<K>这种数据结构,而Map中的值是可以重复的,所以要使用Collection<V>这种数据结构来存储。在这里还要说一下transient关键字,由于AbstractMap的子类HashMap等都实现了Serializable接口,但是在序列化的时候,AbstractMap不希望暴露底层的数据集,所以添加transient,让这两个成员变量不被序列化。我看到有些源码分析说这两个变量被volatile关键字修饰,目的是保证一定的线程安全,但是我在jdk1.8中并没有看到。

抽象方法

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

个人感觉这个地方写的很精髓,这个类中的大部分实现的方法会调用这个方法,而这个方法将底层数据结构的实现方式交给了他的派生类,确实把AbstractMap抽象到了一个很高的高度,极大的降低了和底层的耦合度。而且由于Set的实现方式不同,导致导出的迭代器也不一样,感觉这里特别值得学习。

实例方法

查询方法

     public int size() {
return entrySet().size();
}

此方法返回集合长度

     public boolean isEmpty() {
return size() == 0;
}

判断Map是否为空

     public boolean containsValue(Object value) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (value==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getValue()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (value.equals(e.getValue()))
return true;
}
}
return false;
}

查询Map中是否存在目标值,这里允许保存null

     public boolean containsKey(Object key) {
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return true;
}
}
return false;
}

这里同上,只不过是查询key的值

     public V get(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}

通过key查询value,当value存在时返回value,否则返回null

修改操作

     public V put(K key, V value) {
throw new UnsupportedOperationException();
}

添加新的键值对,如果子类需要修改操作的时候需要重写这个方法,这里我感觉也是这个类的一个亮点,这里并没有留出一个空方法,而是抛出一个异常,是的在别人忘记实现普通方法时可以得到明确的提示

     public V remove(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
Entry<K,V> correctEntry = null;
if (key==null) {
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
correctEntry = e;
}
} else {
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
correctEntry = e;
}
} V oldValue = null;
if (correctEntry !=null) {
oldValue = correctEntry.getValue();
i.remove();
}
return oldValue;
}

当指定的key存在时,会删除对应的键值对,返回对应的值,如果不存在,则返回null。这里要注意的是需要子类重写迭代器的remove方法

     public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}

将指定的Map中的元素放入本Map中,这里要注意如果没有实现put方法会报出异常

     public void clear() {
entrySet().clear();
}

清空Map

视图

返回所有key的set集合:

     public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() {
return i.hasNext();
} public K next() {
return i.next().getKey();
} public void remove() {
i.remove();
}
};
} public int size() {
return AbstractMap.this.size();
} public boolean isEmpty() {
return AbstractMap.this.isEmpty();
} public void clear() {
AbstractMap.this.clear();
} public boolean contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
keySet = ks;
}
return ks;
}

这里可以看到是使用了匿名内部类的方法 ,通过AbstractSet来返回了一个Set,而AbstractSet这个抽象类中看似都实现了方法,但是在他的父类AbstractCollection和他实现的接口Set中,都有未实现的方法iterator()和size()两个抽象方法,所以这两个方法是必须实现的,而在iterator()这个方法中又使用匿名内部类的方式通过接口Iterator来生成一个迭代器,这个接口只有两个方法需要实现hasNext()和next()这两个方法,但是在Iterator中remove()方法只是抛出了一个不支持方法的异常,所以这里仍然需要重写。

返回所有value的collection集合

public Collection<V> values() {
Collection<V> vals = values;
if (vals == null) {
vals = new AbstractCollection<V>() {
public Iterator<V> iterator() {
return new Iterator<V>() {
private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() {
return i.hasNext();
} public V next() {
return i.next().getValue();
} public void remove() {
i.remove();
}
};
} public int size() {
return AbstractMap.this.size();
} public boolean isEmpty() {
return AbstractMap.this.isEmpty();
} public void clear() {
AbstractMap.this.clear();
} public boolean contains(Object v) {
return AbstractMap.this.containsValue(v);
}
};
values = vals;
}
return vals;
}

这个方法和上一个很类似,唯一不同的是第一次使用匿名内部类的时候用的是AbstractCollection,但由于AbstractSet的父类就是AbstractCollection,所以本质并没有什么区别

比较和散列

比较指定的对象与当前Map是否相等,代码很好懂,就不做过多的解释了。

     public boolean equals(Object o) {
if (o == this)
return true; if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false; try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
} return true;
}

返回当前Map的hashcode,对每一个Entry对象都需要计算

     public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}

其他方法

覆盖Object的toString()方法,这里注意编程细节,这里使用了StringBuilder,这种可以追加字符串的类,这里应该是为了效率而放弃了线程安全,没有使用StringBuffer

    public String toString() {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (! i.hasNext())
return "{}"; StringBuilder sb = new StringBuilder();
sb.append('{');
for (;;) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if (! i.hasNext())
return sb.append('}').toString();
sb.append(',').append(' ');
}
}

覆盖Object的clone()实现浅拷贝

     protected Object clone() throws CloneNotSupportedException {
AbstractMap<?,?> result = (AbstractMap<?,?>)super.clone();
result.keySet = null;
result.values = null;
return result;
}

私有静态方法,用来判断两个对象是否相等,这是给接下来两个静态内部类使用的

     private static boolean eq(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}

接下来这两个类都是Map.Entry<K,V>的实现类,一个是可以修改值的,一个是不可修改值的,从命名就可以看出,是给同学们自己实现Map接口的时候,打个样怎么样去实现一个Entry接口,由于这两个类都是静态公有内部类,其访问方式和普通的public类没什么区别,不需要依附于外部类AbstractMap的实例中,并且只能访问AbstractMap的静态区域

SimpleEntry

     public static class SimpleEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
private static final long serialVersionUID = -8499721149061103585L; private final K key;
private V value; /**
* Creates an entry representing a mapping from the specified
* key to the specified value.
*
* @param key the key represented by this entry
* @param value the value represented by this entry
*/
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
} /**
* Creates an entry representing the same mapping as the
* specified entry.
*
* @param entry the entry to copy
*/
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
} public K getKey() {
return key;
} public V getValue() {
return value;
} public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
} public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return eq(key, e.getKey()) && eq(value, e.getValue());
} public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
} public String toString() {
return key + "=" + value;
} }

这里我么你来关注一下hashCode()方法,这里是用key和value做与操作,头一次看见,感觉很特别

SimpleImmutableEntry

     public static class SimpleImmutableEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
private static final long serialVersionUID = 7138329143949025153L; private final K key;
private final V value; public SimpleImmutableEntry(K key, V value) {
this.key = key;
this.value = value;
} public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
} public K getKey() {
return key;
} public V getValue() {
return value;
} public V setValue(V value) {
throw new UnsupportedOperationException();
} public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return eq(key, e.getKey()) && eq(value, e.getValue());
} public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
} public String toString() {
return key + "=" + value;
} }

不同之处在于setValue方法,直接抛出不支持异常

实现一个可以赋值的Map

这个Map实现的很简单,赋值十次以后就错了呦,没有增加扩展功能,喜欢的同学可以自己加一个试试

 public class CanPutMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {

     private Node<K,V> table[];

     private int index;

     private Set<Entry<K,V>> entrySet;

     public CanPutMap() {
table = (Node<K,V>[])new Node[];
index = ;
} public V put(K key, V value) {
V oldValue = null;
for(int i = ; i < table.length; i++) {
if(table[i] != null && table[i].getKey() == key) {
oldValue = table[i].getValue();
table[i].setValue(value);
break;
}
}
if(oldValue == null) {
table[index++] = new Node<K,V>(key, value);
}
return oldValue;
} public V get(Object key) {
V value = null;
for(int i = ; i < table.length; i++) {
if(table[i] != null && table[i].getKey() == key) {
value = table[i].getValue();
break;
}
}
return value;
} @Override
public Set<Entry<K,V>> entrySet() {
// TODO Auto-generated method stub
Set<Entry<K,V>> es;
return (es = entrySet) == null ? entrySet = new MySet(): es;
} final class MySet extends AbstractSet<Entry<K,V>> { @Override
public Iterator<java.util.Map.Entry<K, V>> iterator() {
// TODO Auto-generated method stub
return new MyIterator();
} @Override
public int size() {
// TODO Auto-generated method stub
return ;
} } final class MyIterator implements Iterator<Entry<K,V>> { Node<K,V> current; Node<K,V> next; int index; public MyIterator() {
Node<K,V>[] t = table;
current = next = null;
index = ;
if(t != null && table.length > ) {
do{}while(index < table.length && (next = table[index++]) == null);
}
} @Override
public boolean hasNext() {
// TODO Auto-generated method stub
return next != null;
} @Override
public java.util.Map.Entry<K, V> next() {
// TODO Auto-generated method stub
Node<K,V> e = next;
current = next;
do{}while(index < table.length && (next = table[index++]) == null);
return e;
} public final void remove() {
for(int i = ; i < table.length; i++) {
if(table[i] == current) {
table[i] = null;
}
}
} } public static class Node<K,V> implements Entry<K,V> { private final K key;
private V value; public Node(K key, V value) {
this.key = key;
this.value = value;
} @Override
public K getKey() {
// TODO Auto-generated method stub
return key;
} @Override
public V getValue() {
// TODO Auto-generated method stub
return value;
} @Override
public V setValue(V value) {
// TODO Auto-generated method stub
V oldValue = this.value;
this.value = value;
return oldValue;
} } public static void main(String[] args) {
Map<String, Integer> map = new CanPutMap<String, Integer>();
map.put("", );
map.put("", );
map.put("", );
for(Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "->" + e.getValue());
}
for(Iterator<Entry<String, Integer>> i = map.entrySet().iterator(); i.hasNext();) {
Entry<String, Integer> e = i.next();
System.out.println(e.getKey() + "->" + e.getValue());
if(e.getKey().equals("")) {
i.remove();
}
}
for(Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "->" + e.getValue());
}
} }

随笔2 AbstractMap<K,V>的更多相关文章

  1. 随笔3 HashMap<K,V>

    equals.hashcode和==的区别 在介绍HashMap之前,我想先阐述一下我对这三者的理解,equals这个方法呢,就是在判断是否为同一对象(注意,这里的同一对象和相同的内存地址是不同的), ...

  2. 随笔4 Dictionary<K,V>

    本来说是想介绍一下Hashtable的,但是发现HashMap和Hashtable最开始的不同就是在于HashMap继承了AbstractMap,而Hashtable继承了Dictionary< ...

  3. Java源码 HashMap<K,V>

    HashMap类 https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html public class HashMap<K, ...

  4. 随笔1 interface Map<K,V>

    第一次写笔记就从map开始吧,如上图所示,绿色的是interface,黄色的是abstract class,蓝色的是class,可以看出所有和图相关的接口,抽象类和类的起源都是interface ma ...

  5. java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查

    java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查 一.问题:学习HashMap时候,我做了这样一个程序: impor ...

  6. Java集合源码分析(七)HashMap<K, V>

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  7. JAVA泛型? T K V E等代表的意思

    ? 表示不确定的java类型. T  表示java类型. K V 分别代表java键值中的Key Value. E 代表Element. Object跟这些东西代表的java类型有啥区别呢? Obje ...

  8. Array,ArrayList、List<T>、HashSet<T>、LinkedList<T>与Dictionary<K,V>

    Array: 数组在C#中最早出现的.在内存中是连续存储的,所以它的索引速度非常快,而且赋值与修改元素也很简单. 但是数组存在一些不足的地方.在数组的两个数据间插入数据是很麻烦的,而且在声明数组的时候 ...

  9. ArrayList,Hashtable,List<T>,Dictionary<K,V>

    1.ArrayList ArrayList list = new ArrayList(); //for遍历 ; i < list.Count; i++) { SE se=(SE)list[i]; ...

随机推荐

  1. 9 关联管理器(RelatedManager)

    知识预览: class RelatedManager class RelatedManager "关联管理器"是在一对多或者多对多的关联上下文中使用的管理器.它存在于下面两种情况: ...

  2. 物联网消息队列协议MQTT

    简介Mqtt是一个物联网消息传输协议 mosquitto是mqtt协议的一个开源实现,http://mosquitto.org/ paho是mqtt协议的客户端实现,这里主要用paho的mqtt ja ...

  3. mysql 无法远程连接 没有监听端口

    centos yum安装mysql: 远程连接完成用户授权和防火墙配置,可还是连接不上. 发现mysql没有监听3306端口. 修改mysql配置文件 vi /etc/my.conf 注释掉以下行,重 ...

  4. Linux文件系统启动过程及login的实现

    1. busybox简介 busybox是一个集成了一百多个最常用linux命令和工具的软件,它将许多常用的LINUX命令和工具结合到了一个单独的可执行程序中.虽然与相应的GNU工具比较起来,busy ...

  5. Celery定时任务|计划任务

    适用场景几点几分执行特定的任务 定时任务 配置这个无需多说了和上篇文章一样 任务函数 硬菜来了 添加任务时候的写法 第一种: from celery_task.order_task import or ...

  6. B-/B+树 MySQL索引结构

    索引 索引的简介 简单来说,索引是一种数据结构 其目的在于提高查询效率 可以简单理解为“排好序的快速查找结构” 一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在中磁 ...

  7. 2 Hadoop集群安装部署准备

    2 Hadoop集群安装部署准备 集群安装前需要考虑的几点硬件选型--CPU.内存.磁盘.网卡等--什么配置?需要多少? 网络规划--1 GB? 10 GB?--网络拓扑? 操作系统选型及基础环境-- ...

  8. JMeter性能测试入门-不同类型线程组的使用

    jmeter不同线程组的详解 在做性能测试之前,我们来了解一下JMeter多个不同线程组的应用.首先,JMeter提供了三个基本的线程组,分别为: Thread Group setUp Thread ...

  9. Bootstrap 学习笔记 项目实战 首页内容介绍 下

    最终效果: HTML代码 <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset ...

  10. 应用安全-软件安全-漏洞CVE整理

    jira ssrf CVE-2019-8451 url = url + '/plugins/servlet/gadgets/makeRequest?url=' + host + '@www.baidu ...