这篇把上篇没有聊完的 “JDK8映射扩展方法的重写” 来简单聊聊。
上篇HashMap分析的传送门https://www.jianshu.com/p/715918ac18f4
@Override
public V getOrDefault(Object key, V defaultValue) { // 当Map集合中有这个key时,就使用这个key值,如果没有就使用默认值defaultValue
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
例子如下:
public class Demo {
public static void main(String[] strs){
HashMap<String,String> hashMap = new HashMap<>();
hashMap.put("aa","北京");
String orDefault = hashMap.getOrDefault("aa", "上海");
System.out.println(orDefault); // map中存在"aa",所以值为北京
String valueA = hashMap.getOrDefault("bb", "上海");
System.out.println(valueA); // map中不存在"bb",所以值为上海
}
}
putIfAbsent(key,value) 这个方法:
当value为null的时候,putIfAbsent()方法会覆盖null值,直到value不为null为止
当value初始值不为null的时候,putIfAbsent()保证返回值始终是唯一的,并且是多线程安全的(可以用在单例中)
putIfAbsent()是有返回值的,应该对他的返回值进行非空判断(可以用在单例中)
@Override
public V putIfAbsent(K key, V value) {
return putVal(hash(key), key, value, true, true);
}
// main方法中
HashMap<String,Object> hashMap = new HashMap<>();
Object obj = hashMap.putIfAbsent("cc", null);
if (obj == null) {
System.out.println("打印返回值 : null");
}
hashMap.putIfAbsent("cc",1);
hashMap.putIfAbsent("cc",2);
hashMap.putIfAbsent("dd", 100);
hashMap.putIfAbsent("dd", 200);
Integer i = (Integer)hashMap.putIfAbsent("dd", 300);
hashMap.put("ee", 400);
hashMap.put("ee", 500);
System.out.println(hashMap);
System.out.println(i);
打印结果:
打印返回值 : null
{aa=北京, cc=1, dd=100, ee=500}
100
再来看个移除方法:
@Override
public boolean remove(Object key, Object value) { // 根据key匹配node,如果value也相同则删除
return removeNode(hash(key), key, value, true, true) != null;
}
这个方法和上面源码中的remove(Object key)方法其实是一样的,只是remove(Object key)这个方法需要自己把key,value取出来,最后都是走removeNode(hash(key), key, value, true, true) != null;这个方法进行移除操作的。建议使用这个,效率会高点
接下来是替换方法:
@Override
public boolean replace(K key, V oldValue, V newValue) { // 根据key匹配node,如果value也相同则使用newValue覆盖返回true,否则返回false
Node<K,V> e; V v;
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;
}
@Override
public V replace(K key, V value) { // 根据key匹配node,使用value覆盖原来的值返回原来的老的值,否则返回null
Node<K,V> e;
if ((e = getNode(hash(key), key)) != null) {
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}
computeIfAbsent方法:
根据key做匹配Node,(匹配不到则新建然后重排)如果Node有value,则直接返回oldValue,如果没有value则根据Function接口的apply方法获取value,返回value。
Function接口的apply的入参为key,调用computeIfAbsent时重写Function接口可以根据key进行逻辑处理,apply的返回值即为要存储的value
@Override
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
if (mappingFunction == null)
throw new NullPointerException();
int hash = hash(key); //计算hash
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
// 如果没初始化则先进行初始化,
// resize()方法在table为空时执行初始化逻辑
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
// 下面的逻辑就是通过key找到节点node
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode) //如果已经转为树,按照树的规则进行处理
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
//查找整个链表,找到对应的key
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
V oldValue;
// 如果key存在且其value!=null,则返回该value
if (old != null && (oldValue = old.value) != null) {
afterNodeAccess(old);
return oldValue;
}
}
// 下面的逻辑是:如果没找到则新建节点,
// 节点value为Function返回值;如果找到但value为null,
// 则将Function返回值作为其value
V v = mappingFunction.apply(key); //根据重写逻辑计算返回value
if (v == null) {
return null;
} else if (old != null) {
old.value = v;
afterNodeAccess(old);
return v;
}
else if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else {
//如果匹配不到则table加入数据
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
return v;
}
PS:
存在且value不为null,则返回value
不满足上述条件下,先检查Function返回值,若为null,返回null
存在但value为null,则将Function返回值作为value,返回value
不存在,新建节点,将Function返回值作为value,返回value
computeIfPresent方法:
根据key做匹配,如果匹配不上则返回null,匹配上根据BiFunction的apply方法获取value,返回value。BiFunction接口的apply的入参为key、oldValue,调用computeIfPresent时重写Function接口可以根据key和oldValue进行逻辑处理,apply的返回值如果为null则删除该节点,否则即为要存储的value
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
Node<K,V> e; V oldValue;
int hash = hash(key);
if ((e = getNode(hash, key)) != null &&
(oldValue = e.value) != null) {
//使用key和原value作为入参
V v = remappingFunction.apply(key, oldValue);
if (v != null) {
e.value = v;
afterNodeAccess(e);
return v;
}
else
removeNode(hash, key, null, false, true);
}
return null;
}
PS:
1.存在且value不为null:1,BiFunction返回值为null,删除该节点;2,BiFunction返回值不为null,作为新value,返回其值
2.不存在或其value为null,返回null
compute方法:
根据key做匹配,根据BiFunction的apply返回做存储的value。匹配到Node做value替换,匹配不到新增node。apply的返回值如果为null则删除该节点,否则即为要存储的value
@Override
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
V oldValue = (old == null) ? null : old.value;
//使用key和原value作为入参
V v = remappingFunction.apply(key, oldValue);
if (old != null) {
if (v != null) {
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
}
else if (v != null) {
if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else {
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
}
return v;
}
PS: 如果key不存在,新建key进行存储;如果key存在,value为null则删除此节点,不为null替换节点value并返回此value
merge方法:
功能大部分与compute相同,不同之处在于BiFunction中apply的参数,入参为oldValue、value,调用merge时根据两个value进行逻辑处理并返回value
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null)
throw new NullPointerException();
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
if (old != null) {
V v;
if (old.value != null)
//使用新老value作为入参
v = remappingFunction.apply(old.value, value);
else
v = value;
if (v != null) {
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
return v;
}
if (value != null) {
if (t != null)
t.putTreeVal(this, tab, hash, key, value);
else {
tab[i] = newNode(hash, key, value, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
}
return value;
}
forEach方法:
调用此方法时实现BiConsumer接口重写void accept(Object o, Object o2)方法,其中o为key,o2为value,可根据自己的实现对map中所有数据进行处理
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
replaceAll方法:
调用此方法时重写BiFunction的Object apply(Object o, Object o2)方法,其中o为key,o2为value,根据重写方法逻辑进行重新赋值
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Node<K,V>[] tab;
if (function == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
e.value = function.apply(e.key, e.value);
}
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
JDK8映射扩展方法的重写到这里就结束了,下面扩展一下HashMap的clone()方法
这个方法最终是继承于Object的方法
这里涉及到一个概念:
浅拷贝:对一个对象进行clone生成新的对象,新的对象要开辟一块新的内存来存储,新对象中的基本类型属性和String类型属性都会开辟新的空间存储,但是如果是引用类型的属性,那这个引用类型的属性还是指向原对象的引用属性内存,当对新的对象或原对象的引用属性做出改变的时候,两方的引用属性类型的值同时做出改变
/**
* Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and
* values themselves are not cloned.
* key和value不被复制
* @return a shallow copy of this map
*/
@SuppressWarnings("unchecked")
@Override
public Object clone() {
HashMap<K,V> result;
try {
result = (HashMap<K,V>)super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.reinitialize(); // 重置为初始默认状态
result.putMapEntries(this, false); // 实现Map.putAll和Map构造函数
return result;
}
PS:
1.HashMap的clone方法生成新的对象,新的对象有自己独立的存储空间。
2.虽然HashMap的clone方法生成了新的对象,但新的HashMap和原来的HashMap所存储的引用类型都是指向的同一存储空间。
这里用个简单例子来论证一下:
public class Person {
private String name;
private String sex;
public Person(String name, String sex) {
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
public static void main(String[] strs){
HashMap<String,Object> hashMap = new HashMap<>();
// hashMap.put("aa","北京");
// String orDefault = (String) hashMap.getOrDefault("aa", "上海");
// System.out.println(orDefault); // map中存在"aa",所以值为北京
// String valueA = (String) hashMap.getOrDefault("bb", "上海");
// System.out.println(valueA); // map中存在"bb",所以值为上海
//
// Object obj = hashMap.putIfAbsent("cc", null);
// if (obj == null) {
// System.out.println("打印返回值 : null");
// }
// hashMap.putIfAbsent("cc",1);
// hashMap.putIfAbsent("cc",2);
// hashMap.putIfAbsent("dd", 100);
// hashMap.putIfAbsent("dd", 200);
// Integer i = (Integer)hashMap.putIfAbsent("dd", 300);
// hashMap.put("ee", 400);
// hashMap.put("ee", 500);
// System.out.println(hashMap);
// System.out.println(i);
Person person = new Person("lucy","女");
hashMap.put("lucy", person);
HashMap<String,Object> cloneMap = (HashMap<String, Object>) hashMap.clone();
System.out.println("************************* 不做改变 ***********************************");
System.out.println("未改变之前, hashMap的值:"+hashMap.toString());
System.out.println("未改变之前,cloneMap的值:"+cloneMap.toString());
System.out.println("hashMap和cloneMap是否指向同一内存地址:"+(hashMap==cloneMap));
System.out.println("hashMap和cloneMap中存储的Person是否指向同一内存地址:"+(hashMap.get("lucy")==cloneMap.get("lucy")));
System.out.println();
System.out.println("************************* 对cloneMap中的值进行改变,看是否能影响到hashMap ***********************************");
Person clonePerson = (Person) cloneMap.get("lucy");
clonePerson.setSex("男");
System.out.println("改变之后,cloneMap的值:" + cloneMap.toString());
System.out.println("改变之后, hashMap的值:" + hashMap.toString());
System.out.println();
System.out.println("************************* 对hashMap新增 **********************************");
Person newPerson = new Person("jack","男");
hashMap.put("jack", newPerson);
System.out.println("改变之后,cloneMap的值:" + cloneMap.toString());
System.out.println("改变之后, hashMap的值:" + hashMap.toString());
}
打印出的数据:
************************* 不做改变 ***********************************
未改变之前, hashMap的值:{lucy=Person{name='lucy', sex='女'}}
未改变之前,cloneMap的值:{lucy=Person{name='lucy', sex='女'}}
hashMap和cloneMap是否指向同一内存地址:false
hashMap和cloneMap中存储的Person是否指向同一内存地址:true
************************* 对cloneMap中的值进行改变,看是否能影响到hashMap ***********************************
改变之后,cloneMap的值:{lucy=Person{name='lucy', sex='男'}}
改变之后, hashMap的值:{lucy=Person{name='lucy', sex='男'}}
************************* 对hashMap新增 **********************************
改变之后,cloneMap的值:{lucy=Person{name='lucy', sex='男'}}
改变之后, hashMap的值:{lucy=Person{name='lucy', sex='男'}, jack=Person{name='jack', sex='男'}}
总结:
从上可以看出clone方法虽然生成了新的HashMap对象,新的HashMap中的table数组虽然也是新生成的,但是数组中的元素还是引用以前的HashMap中的元素。这就导致在对HashMap中的元素进行修改的时候,即对数组中元素进行修改,会导致原对象和clone对象都发生改变,但进行新增或删除就不会影响对方,因为这相当于是对数组做出的改变,clone对象新生成了一个数组
网友评论