走进科学之——为何刚初始化的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的原因。内部类EntrySet
的Iterator
方法返回的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