按照下面的代码去遍历 Map 集合,可以获取到存入到 Map 的所有数据,这样是没有任何问题的,但是深入源码却发现有点问题:
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> next = iterator.next(); String key = next.getKey(); String value = next.getValue(); System.out.println("key = "+key+";value = "+value); }HashMap.entrySet() 是获取 HashMap 封装了 key-value 的 Map.Entry 所有对象。
public Set<Map.Entry<K,V>> entrySet() { return entrySet0(); }内部调用了 entrySet0() 方法
private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); }EntrySet 的源码
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K,V> e = (Map.Entry<K,V>) o; Entry<K,V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } 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) { HashMapEntry<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 (HashMapEntry<K,V> e = tab[i]; e != null; e = e.next) { action.accept(e); // Android-modified - this was outside of the loop, inconsistent with other // collections if (modCount != mc) { throw new ConcurrentModificationException(); } } } } } }从上面的调用关系来看,最后返回一个 EntrySet 对象而已,EntrySet 是无参构造实例化的,查看源码它并没有显示的提供无参构造,也就是说没有做什么操作,只是把 EntrySet 对象返回而已。 那我们在调用 map.entrySet() 处打断点发现,EntrySet 内部却是有值的,这就奇怪的,这些值是哪来的,它并没有操作存储数据的 table 数组啊,那怎么会有数据呢?
下面是断点的图解:
下面的测试例子
class T { private String name; public T() { } //测试断点的情况下是否会调用 toString() 方法 @Override public String toString() { this.name = "abc"; System.out.println("输出了:" + name); return super.toString(); } } main()方法 System.out.println("start"); T t = new T(); System.out.println("end");没有断点的情况的输出结果: System.out: start System.out: end
断点的情况的输出结果: System.out: start System.out: 输出了:abc System.out: end
在这里可以得出结论就是对一个对象打断点会执行该对象的 toString() 方法。同理在我们给 entries 打断点时可以看的返回的 EntrySet 集合是有数据的,可以知道这个数据肯定就是在 EntrySet 中的 toString() 方法获取的,查阅源码可以知道, EntrySet 并没有实现 toString 方法,而是在祖父类 AbstractCollection 实现了,代码如下:
public String toString() { //实际上就是获取 Iterator 对象,然后不断地调用 next() 方法而得到的值。 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(' '); } }若是不明在其中的调用关系可以查阅我之前的博客 LinkedHashMap 是如何能作为最近最少使用算法底层数据结构的?
结论:在获取 EntrySet 之后,我们一般调用 iterator() 获取 Iterator 对象,然后不断的调用 next() 方法,实际上这个才是真正获取数据的地方。它内部遍历 table 数组来获取指定的数据。