遍历 Map 中 EntrySet 集合一点思考。

按照下面的代码去遍历 Map 集合,可以获取到存入到 Map 的所有数据,这样是没有任何问题的,但是深入源码却发现有点问题:

Iterator> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry 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> entrySet() {
    return entrySet0();
}

内部调用了 entrySet0() 方法

private Set> entrySet0() {
    Set> es = entrySet;
    return es != null ? es : (entrySet = new EntrySet());
}

EntrySet 的源码

private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public Iterator> iterator() {
        return newEntryIterator();
    }
    public boolean contains(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry) o;
        Entry 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> spliterator() {
        return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumersuper Map.Entry> action) {
        HashMapEntry[] 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 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 数组啊,那怎么会有数据呢?

下面是断点的图解:

遍历 Map 中 EntrySet 集合一点思考。_第1张图片

2、在对某个对象进行 add to watch 实际上调用该对象的 toString 方法。

下面的测试例子

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 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 是如何能作为最近最少使用算法底层数据结构的?

遍历 Map 中 EntrySet 集合一点思考。_第2张图片

结论:在获取 EntrySet 之后,我们一般调用 iterator() 获取 Iterator 对象,然后不断的调用 next() 方法,实际上这个才是真正获取数据的地方。它内部遍历 table 数组来获取指定的数据。

你可能感兴趣的:(遍历 Map 中 EntrySet 集合一点思考。)