4

Here is the keySet() function inside java.util.HasMap class :

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

In the comment, it says this function

Returns a {@link Set} view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa.

Therefore, I expected the object of KeySet type, which this functions returns would contain a reference to the "view of keys". However when I look at the code, the KeySet class does not contain any field at all, and all of its super classes either.

final class KeySet extends AbstractSet<K> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
    public final Iterator<K> iterator()     { return new KeyIterator(); }
    public final boolean contains(Object o) { return containsKey(o); }
    public final boolean remove(Object key) {
        return removeNode(hash(key), key, null, false, true) != null;
    }
    public final Spliterator<K> spliterator() {
        return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumer<? super K> 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.key);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

Could somebody please explain

  1. What does "a view of the keys" mean? What kind of data is "a view"?
  2. How can the HashMap.keySet() returns a view of the keys by using KeySet object, which doesn't contain any reference to anything at all?

To make it more clear :

Here is a code sample to print out the keyset value. Although the KeySet object holds the reference to the containing map data, how can this exactly output the key data but not other data of the map (not value data or anything else). What is the thing that tells this KeySet object to hold only MapKeys? I can't see such instruction in its code.

package com.tutorialspoint;
import java.util.*;

public class HashMapDemo {
   public static void main(String args[]) {

     // create hash map
     HashMap newmap = new HashMap();

     // populate hash map
     newmap.put(1, "tutorials");
     newmap.put(2, "point");
     newmap.put(3, "is best");

     // get keyset value from map
     Set keyset = newmap.keySet();

     // check key set values
     System.out.println("Key set values are: " + keyset);
   }    
}

Ouput :

Key set values are: [1, 2, 3]

Bonsaisteak
  • 151
  • 1
  • 8
  • As an aside, your `HashMapDemo` uses [raw types](https://stackoverflow.com/q/2770321/113632), which you should avoid. Use `HashMap` and `Set` here. – dimo414 Jul 25 '18 at 07:48

1 Answers1

9
  1. A "view" is a type of object whose data is backed by a different object, but is provided in a different way. In this case, it's providing a "view" of the Map's keys as a Set. This has a number of benefits, both for users and for performance.

    Notably, since it shares its data with the backing class there's very little memory overhead - it doesn't need to copy all the keys into a whole new Set. Additionally, users don't need to worry about the view getting out of sync with the backing structure - additions and removals will be visible via the view immediately.

  2. Because KeySet is an inner class it has access to the fields of an instance of its containing class (HashMap). Notice the HashMap.this notation in your code snippet.

    • size()'s return size; is a reference to HashMap's size field
    • clear()'s HashMap.this.clear(); calls HashMap's clear() method (it needs to use HashMap.this to reference the map's clear() method rather than its own)
    • contains() delegates HashMap's containsKey() method - this doesn't require HashMap.this because KeySet doesn't have a colliding containsKey() method
    • remove() similarly delegates to HashMap's removeNode()

    If it was instead declared as final static class KeySet it would be a static nested class, which is not tied to an instance of the containing class (it's just a way of organizing related classes). In that case KeySet would need an explicit HashMap field, and the map in question would need to be passed into the constructor. Inner classes make this implicit (which is concise, but certainly sometimes confusing).

  3. What is the thing that tells this KeySet object to hold only MapKeys?

    To be clear, there is no such instruction. A KeySet instance transitively has access to all the fields and methods of the backing map. However your example (System.out.println("Key set values are: " + keyset);) is implicitly calling keyset.toString(), which is (intentionally) not implemented to return the map's values. Because KeySet extends AbstractSet (which in turn extends AbstractCollection) its .toString() relies on the KeySet's iterator() implementation, which provides an iterator over the map's keys, not its values.

dimo414
  • 47,227
  • 18
  • 148
  • 244
  • Thanks for your answer. I still don't understand about the meaning of " it's providing a "view" of the Map's keys as a Set". Do you mean the "Set" mentioned here will be created on the memory containing the HashMap keys as its elements? If not, what does it come from? – Bonsaisteak Jul 20 '18 at 06:29
  • @Bonsaisteak the the key-elements are only stored in the `HashMap`. The `KeySet` does not store anything, the only "field" it has is the implicit reference to the containing Map. All methods of `KeySet` use this implicit reference to access or even modify the state of the Map. – Hulk Jul 20 '18 at 07:48
  • 1
    @Bonsaisteak yes, the `Set` reflects the contents of the `Map` (specifically its keys). It doesn't do any copying or construction of new objects - `KeySet` simply calls into the backing `Map`. Take a look at `contains()`, for example - it simply calls `containsKey()` on the map (`HashMap.this` can be omitted since `KeySet` doesn't have a `containsKey()` method). – dimo414 Jul 20 '18 at 08:29
  • @shmosel Understood that KeySet holds an reference to the containing map. However, I still don't understand what is the thing that specify its connection with containing map's keys but not other data? – Bonsaisteak Jul 25 '18 at 03:07
  • @Bonsaisteak could you clarify your question (perhaps in an edit to the original post, which will give you more space)? `KeySet` has access to *all* of the map's data, simply by being an inner class. Since its purpose is to surface information about the map's keys, that's all the class actually touches. – dimo414 Jul 25 '18 at 03:11
  • Thank you, I have just edited my question. Please refer to the part after "To make it more clear...". – Bonsaisteak Jul 25 '18 at 03:35
  • @Bonsaisteak expanded on my answer. – dimo414 Jul 25 '18 at 07:45