按照下面的代碼去遍歷 Map 集合,可以獲取到存入到 Map 的所有數(shù)據(jù)屈呕,這樣是沒有任何問題的,但是深入源碼卻發(fā)現(xiàn)有點問題:
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);
}
1棺亭、遍歷的源碼分析
HashMap.entrySet() 是獲取 HashMap 封裝了 key-value 的 Map.Entry 所有對象虎眨。
public Set<Map.Entry<K,V>> entrySet() {
return entrySet0();
}
內(nèi)部調(diào)用了 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();
}
}
}
}
}
}
從上面的調(diào)用關(guān)系來看,最后返回一個 EntrySet 對象而已,EntrySet 是無參構(gòu)造實例化的嗽桩,查看源碼它并沒有顯示的提供無參構(gòu)造钟鸵,也就是說沒有做什么操作,只是把 EntrySet 對象返回而已涤躲。
那我們在調(diào)用 map.entrySet() 處打斷點發(fā)現(xiàn)棺耍,EntrySet 內(nèi)部卻是有值的,這就奇怪的种樱,這些值是哪來的蒙袍,它并沒有操作存儲數(shù)據(jù)的 table 數(shù)組啊,那怎么會有數(shù)據(jù)呢嫩挤?
下面是斷點的圖解:
2害幅、在對某個對象進行 add to watch 實際上調(diào)用該對象的 toString 方法。
下面的測試例子
class T {
private String name;
public T() {
}
//測試斷點的情況下是否會調(diào)用 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");
沒有斷點的情況的輸出結(jié)果:
System.out: start
System.out: end
斷點的情況的輸出結(jié)果:
System.out: start
System.out: 輸出了:abc
System.out: end
在這里可以得出結(jié)論就是對一個對象打斷點會執(zhí)行該對象的 toString() 方法岂昭。同理在我們給 entries 打斷點時可以看的返回的 EntrySet 集合是有數(shù)據(jù)的以现,可以知道這個數(shù)據(jù)肯定就是在 EntrySet 中的 toString() 方法獲取的,查閱源碼可以知道约啊, EntrySet 并沒有實現(xiàn) toString 方法邑遏,而是在祖父類 AbstractCollection 實現(xiàn)了,代碼如下:
public String toString() {
//實際上就是獲取 Iterator 對象恰矩,然后不斷地調(diào)用 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(' ');
}
}
若是不明在其中的調(diào)用關(guān)系可以查閱我之前的博客 LinkedHashMap 是如何能作為最近最少使用算法底層數(shù)據(jù)結(jié)構(gòu)的?
結(jié)論:在獲取 EntrySet 之后外傅,我們一般調(diào)用 iterator() 獲取 Iterator 對象纪吮,然后不斷的調(diào)用 next() 方法,實際上這個才是真正獲取數(shù)據(jù)的地方萎胰。它內(nèi)部遍歷 table 數(shù)組來獲取指定的數(shù)據(jù)碾盟。