走进科学之——为何刚初始化的HashMap的entrySet不为null

疑团重重

当你调试以下代码时,你会发现oldMap的entrySet不为null:

HashMap<Object,Integer> oldMap = new HashMap<Object,Integer>();
System.out.println();//此处打断点

在这里插入图片描述
从图中可以看出,oldMap的各个成员都是刚初始化时的状态,除了entrySet这个成员不为null以外(后面有个@500,代表这个引用已经指向了某个对象了),但HashMap的默认构造器只是:

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

很明显,默认构造器只是初始化了loadFactor而已,肯定没有去初始化entrySet成员啊。

谜题揭秘

其实是因为debug调试时,隐式地调用了AbstractMap的toString方法。

如果你通过反射的方法来看:

HashMap<Object,Integer> map = new HashMap<>();

Field entrySetField = HashMap.class.getDeclaredField("entrySet");
entrySetField.setAccessible(true);
Object entrySet = entrySetField.get(map);
System.out.println("entrySet = " + entrySet);
System.out.println("map.toString() = " + map.toString());
entrySet = entrySetField.get(map);
System.out.println("entrySet = " + entrySet);

你会发现打印结果为:

entrySet = null  //调用toString之前,entrySet还是为null的
map.toString() = {}
entrySet = []    //调用toString之后,entrySet就不为null了

从打印结果可以看出,调用toString之后,entrySet会被赋值,不再为null。

调用过程

首先,我们先来看看map.toString() = {}这句后面的大括号是怎么打印出来。首先AbstractMap.toString()会被调用:

//AbstractMap.java
    public String toString() {
        Iterator<Entry<K,V>> i = entrySet().iterator();//调用entrySet方法返回值的iterator方法
        if (! i.hasNext())
            return "{}";

        StringBuilder sb = new StringBuilder();
        sb.append('{');//这不就是大括号吗
        for (;;) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            sb.append(key   == this ? "(this Map)" : key);
            sb.append('=');
            sb.append(value == this ? "(this Map)" : value);
            if (! i.hasNext())
                return sb.append('}').toString();//这不就是大括号吗
            sb.append(',').append(' ');
        }
    }

再来看entrySet方法,它会创建成员内部类EntrySet的实例,然后赋值给HashMap的entrySet成员,这就是刚创建的HashMap的entrySet不为null的原因。内部类EntrySetIterator方法返回的EntryIterator迭代器,才让AbstractMap.toString()打印出了东西。

    public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;//如果为null,创建这个内部类的实例
    }

    final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator();
        }
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return removeNode(hash(key), key, value, true, true) != null;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super Map.Entry<K,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);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

然后,我们先来看看entrySet = []这句后面的大括号是怎么打印出来。首先AbstractCollection.toString()会被调用:

//AbstractCollection.java
    public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');//这不就是中括号吗
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();//这不就是中括号吗
            sb.append(',').append(' ');
        }
    }

上面的Iterator<E> it = iterator()会调用到EntrySet内部类重写的iterator方法,所以这里返回的迭代器也是EntryIterator迭代器,但由于HashMap刚创建,迭代器根本没有元素可以迭代。

以上,就解释了为什么调试截图中oldMap的打印结果为"{}",entrySet成员的打印结果是"[]"

友情链接

how and when HashMap initialize entrySet and add value into it

posted @ 2020-01-02 20:42  allMayMight  阅读(400)  评论(0编辑  收藏  举报