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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值