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的引用啊。
在这里插入图片描述

浅的程度

在这里插入图片描述
到了虚线下面,左右两边的引用就是一样的了。

发布了171 篇原创文章 · 获赞 130 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/anlian523/article/details/103797056