JDK8 HashMap源码 clone解析
分析源码
Returns a shallow copy of this HashMap
instance: the keys and values themselves are not cloned.
英文注释已经说了这个一个浅拷贝操作,但到底浅到什么程度呢,接下来本文将进行详细分析。
public Object clone() {
HashMap<K,V> result;
try {
result = (HashMap<K,V>)super.clone();//.的优先级高,之后再强转。这个result确实是新创建的
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.reinitialize();
result.putMapEntries(this, false);
return result;
}
super.clone()
这句会调用到AbstractMap的clone方法:
protected Object clone() throws CloneNotSupportedException {
AbstractMap<?,?> result = (AbstractMap<?,?>)super.clone();
result.keySet = null;
result.values = null;
return result;
}
看起来AbstractMap的clone方法帮我们清空了keySet和values成员,这两个成员是作为视图来使用的,所以早点清空也好。
super.clone()
这句会调用到Object的clone方法:
protected native Object clone() throws CloneNotSupportedException;
这下调到了native方法了,无法看实现了,那只好看看注释了:
this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a “shallow copy” of this object, not a “deep copy” operation.
英文注释说:克隆出来的对象的各个field,完全是通过this对象的各个field赋值过去的。所以如果某个field是引用类型,那么克隆对象的这个field指向的是同一个对象。
分析过程
public Object clone() {
HashMap<K,V> result;
try {
result = (HashMap<K,V>)super.clone();//.的优先级高,之后再强转。这个result确实是新创建的
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.reinitialize();
result.putMapEntries(this, false);
return result;
}
- 执行完
result = (HashMap<K,V>)super.clone()
后,此时你如果判断result == this
,那么将会返回false,说明最起码这两个引用指向的是不同的HashMap了。而根据上一章的分析,则此时result
的各个field和this
的各个field如果进行==
判断,那么肯定都会返回true,这说明field的引用指向的是同一个对象。此时,新HashMapresult
只是套了一个新壳子,里面的成员全是旧的。 - 执行完这句
result.reinitialize()
后,那么result
的各个field全部会被设置为默认值(基本类型归零,引用类型为null)。此时,新HashMapresult
的各个field全部会被设置为默认值,已经与旧的field无关了。 - 执行
result.putMapEntries(this, false)
时,由于putMapEntries的函数逻辑,会对table成员进行初始化(通过else if (s > threshold) resize();
),并不断往table里添加元素。此时,新HashMapresult
的table filed会被设置为新对象。
debug调试
import java.util.*;
public class test2 {
public static void main(String[] args) {
HashMap<Object,Integer> oldMap = new HashMap<Object,Integer>();
for(int i=0;i<12;i++){
oldMap.put(new Object(),i);
}
HashMap<Object,Integer> newMap = (HashMap<Object,Integer>)oldMap.clone();//此处打断点,并force step into
System.out.println();
}
}
下图看出,现在刚调用了result = (HashMap<K,V>)super.clone();
,已经进入了HashMap的clone()源码了。下面刚执行完result = (HashMap<K,V>)super.clone();
,即现在是把Object的native的clone方法返回值返回给了AbstractMap的clone方法,并返回到了HashMap的源码了。this
就是oldMap,而result
就是newMap。对比两个map的成员发现,两个table成员的编号都是@501
,两个entrySet成员的编号都是@502
,这说明两个table引用指向的是同一个对象,要是==
判断,肯定会返回true的;entrySet同理。
所以:此时,新HashMapresult
只是套了一个新壳子,里面的成员全是旧的。
下图看出,现在刚调用了result.reinitialize();
,这个函数会把所有成员置为默认值,引用类型置为null。所以result
的table成员此时为null。但此时entrySet却很奇怪(@517
,已经有值了),这是因为调试器隐式调用了toString方法所导致的,具体请看本人博客——为何刚创建的HashMap的entrySet不为null,这里你就当做提前给entrySet成员赋值吧。
所以:此时,新HashMapresult
的各个field全部会被设置为默认值,已经与旧的field无关了。
下图看出,现在刚调用了result.putMapEntries(this, false);
,此时,新HashMapresult
的table filed会被设置为新对象。
putMapEntries里使用oldMap的迭代器,迭代过程中调用putVal,putVal里会调用newNode(hash, key, value, null)
创建新的Node实例,所以table成员里,各个Node对象也是各不相等的。
但Node实例的成员key和value成员却是相同引用(从下面的重复出现的@6073f712
可以看出),因为当初调用putVal(hash(key), key, value, false, evict);
时,就直接用的key和value的引用啊。
浅的程度
到了虚线下面,左右两边的引用就是一样的了。