欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JDK8 HashMap源码 clone解析

程序员文章站 2022-05-25 11:47:05
...

分析源码

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只是套了一个新壳子,里面的成员全是旧的。

JDK8 HashMap源码 clone解析
下图看出,现在刚调用了result.reinitialize();,这个函数会把所有成员置为默认值,引用类型置为null。所以result的table成员此时为null。但此时entrySet却很奇怪(@517,已经有值了),这是因为调试器隐式调用了toString方法所导致的,具体请看本人博客——为何刚创建的HashMap的entrySet不为null,这里你就当做提前给entrySet成员赋值吧。
所以:此时,新HashMapresult的各个field全部会被设置为默认值,已经与旧的field无关了。
JDK8 HashMap源码 clone解析
下图看出,现在刚调用了result.putMapEntries(this, false);此时,新HashMapresult的table filed会被设置为新对象。
JDK8 HashMap源码 clone解析
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的引用啊。
JDK8 HashMap源码 clone解析

浅的程度

JDK8 HashMap源码 clone解析
到了虚线下面,左右两边的引用就是一样的了。

相关标签: Java