tl;dr Yes.
I believe the iteration order for .keySet() and .values() is consistent (Java
8).
Proof 1: We load a HashMap with random keys and random values. We iterate on this HashMap using .keySet() and load the keys and it's corresponding values to a LinkedHashMap (it will preserve the order of the keys and values inserted). Then we compare the .keySet() of both the Maps and .values() of both the Maps. It always comes out to be the same, never fails.
public class Sample3 {
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static SecureRandom rnd = new SecureRandom();
// from here: https://stackoverflow.com/a/157202/8430155
static String randomString(int len){
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
sb.append(AB.charAt(rnd.nextInt(AB.length())));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
for (int j = 0; j < 10; j++) {
Map<String, String> map = new HashMap<>();
Map<String, String> linkedMap = new LinkedHashMap<>();
for (int i = 0; i < 1000; i++) {
String key = randomString(8);
String value = randomString(8);
map.put(key, value);
}
for (String k : map.keySet()) {
linkedMap.put(k, map.get(k));
}
if (!(map.keySet().toString().equals(linkedMap.keySet().toString()) &&
map.values().toString().equals(linkedMap.values().toString()))) {
// never fails
System.out.println("Failed");
break;
}
}
}
}
Proof 2: From here, the table is an array of Node<K,V> class. We know that iterating an array will give the same result every time.
/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;
The class responsible for .values():
final class Values extends AbstractCollection<V> {
// more code here
public final void forEach(Consumer<? super V> 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.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
The class responsible for .keySet():
final class KeySet extends AbstractSet<K> {
// more code here
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();
}
}
}
Carefully look at both the inner classes. They are pretty much the same except:
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); <- from KeySet class
// action.accept(e.value); <- the only change from Values class
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
They iterate on the same array table to support .keySet() in KeySet class and .values() in Values class.
Proof 3: this answer also explicitly states - So, yes, keySet(), values(), and entrySet() return values in the order the internal linked list uses.
Therefore, the .keySet() and .values() are consistent.