Question

Recently when I looked over HashMap, I found an interesting thing about the keySet method (and then asked a question on StackOverflow).

The source code of HashMap.keySet() is shown as follows

    public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

As you can see, when the keySet() method first called, it just returns a KeySet object. The KeySet object is a subclass of AbstractSet with an empty constructor, and contains no element.

Source codes of KeySet and AbstractSet are shown below:

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
    /**
     * Sole constructor.  (For invocation by subclass constructors, typically
     * implicit.)
     */
    protected AbstractSet() {
    }

    // some other methods
}

But when you call the method like this

Collection<String> keySet = map.keySet();
System.out.println(keySet);

the method returns a set rapidly, and you can get a set of keys shown on the screen.

Not only keySet(), but also values() and entrySet() method could do similar things.

What happened here? Is there any magical thing in Java?

Yes, there is really a "magician" do some incredible things here, and his magic wand is called Iterator.


Answer

Let's start off with how you would use the method in a program. When you first call keySet, it returns a KeySet object. However, the object does contain an implicit reference to the HashMap object created it which means it's an inner class of HashMap rather than contains nothing.

Now we get an Set object, but it contains no element of the given type(that is, the generic type of HashMap's key). Then we use a println method to print the set. When println is ready to print a non-String object, it will call object.toString() method to change the object to a String object like this

    public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
    }

AbstractCollection uses an iterator to iterate its elements in toString method

    public String toString() {
        // iterator() method is called here
        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(' ');
        }
    }

And now we find something familiar -- the iterator method. As we talked above, this method would return a newKeyIterator object, which also associated to the HashMap object and then used by toString method to iterate such HashMap object. Then, we can get a "set" of keys after toString method returned. Finally the "set" will be printed by println method, and we also know how that "magic wand" works.


Some more things

Someone uses an IDE(like Eclipse) to develop program might have another question: when a breakpoint is set at the Collection<String> keySet = map.keySet(); line in IDE, run the program as debug mode, and step over the line, the IDE will show that the variable values has been a collection filled with all keys of Hashmap, rather than an object only with an reference to the HashMap object, what happened here?

You might have guessed that "Someone" is me, and yep, you are right :D I knew the iterator things but was still confused with this question for a long time. The question comes from the fact that when moving the mouse to hover over the values variable, or just clicking the values variable in the Variables view, there would be a small window with all keys in the keySet variable. Nevertheless, that's actually not a question as clicking variable in the Variables view just means executing the toString method and you know what will happen next.


Conclusion

Inner classes KeySet, Values and EntrySet do not contains any explicit fields except a implicit reference to the HashMap object which created them. Every time you call keySet(), values() or entrySet() methods, they just return such "empty" objects, and do nothing until iterator method is called. That means the so-called keySet, values and entrySet are all varied with the HashMap object contemporaneously. That's why we can always get the latest mapper of the related HashMap object.


Thank @mastov and @Eran for excellent answers.

Every time I take a look at source codes of kinds of frameworks, I can always find some interesting new things. That's actually what makes programming so charming :-)



Comments

comments powered by Disqus