JDK 8 en mac OS, observando el siguiente código de HashMap.java:
public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; }Cualquier cambio en los ks devueltos se reflejará en keySet, ya que siempre apuntan al mismo conjunto subyacente, si esto es cierto, ¿se puede escribir como:
public Set<K> keySet() { if (keySet == null) { keySet = new KeySet(); } return keySet; }¿Los dos fragmentos de código se comportan de forma equivalente?
Si es así, ¿por qué HashMap usa la primera variación en lugar de la segunda variación?
El almacenamiento en caché en una variable local se realiza para mejorar el rendimiento. El código de bytes generado es más pequeño, el campo se lee una vez y, por lo tanto, una pérdida de caché podría ocurrir solo una vez, y algunas otras cosas.
Esta es una optimización bastante avanzada y debe llevarse a cabo solo en fragmentos de código que se ejecutan con mucha frecuencia. La razón por la que se aplicó aquí probablemente se deba a que HashMap se escribió en Java 1.2, cuando el JIT era muy básico y, por lo tanto, cosas como estas tuvieron un impacto considerable.
En este caso, también se hace para admitir el acceso de subprocesos múltiples. HashMap no está sincronizado, sin embargo, se puede compartir a través de una publicación segura si luego no se modifica. Si dos subprocesos ejecutan el método simultáneamente, podría ocurrir una condición de carrera: la primera lectura en if(keySet == null) podría leer un valor más nuevo escrito por otro subproceso y la segunda lectura en return keySet; lea el valor anterior ( null ). El uso de una variable local garantiza que if y return usen la misma referencia cuando no sean nulos. Por lo tanto, nunca puede devolver null .
La variable local se guarda solo como una optimización como lo señala @Fransesco. También evita la creación de nuevos objetos en algunos casos.
La implementación no almacena ningún estado internamente y opera en el mapa hash subyacente para todas las operaciones y se esperan cambios en el conjunto según los documentos de Java.
Para referencia
/** * 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. If the map is modified * while an iteration over the set is in progress (except through * the iterator's own <tt>remove</tt> operation), the results of * the iteration are undefined. The set supports element removal, * which removes the corresponding mapping from the map, via the * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> * operations. It does not support the <tt>add</tt> or <tt>addAll</tt> * operations. * * @return a set view of the keys contained in this map */ public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; } 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(); } } }AFAIK, el comportamiento es el mismo en todas las plataformas, no solo en Mac