keySet() and values()
Get a map's keys or values as live views, know what removing through them does, and copy them before handing them to other code.
Set<K> keySet()
Collection<V> values()- Added in Java Java 1.2
Return value
Set<K> / Collection<V>keySet returns a set view of the keys; values returns a collection view of the values. Both are backed by the map.
Errors
None directly. Adding to either view throws UnsupportedOperationException; removing through them throws it too when the map is unmodifiable; iterating while the map is structurally modified throws ConcurrentModificationException on HashMap, LinkedHashMap and TreeMap.
keySet() returns the map's keys as a Set<K>. values() returns the values as a Collection<V>. Neither is a copy. Both are views backed by the map, in the map's iteration order.
Live views
A view always reflects the map. Keep a reference to keySet(), add an entry to the map, and the set shows it. The other direction works for removal: keySet().remove(k), keySet().retainAll(allowed) and values().removeIf(...) delete the matching mappings from the map. Adding to a view is not possible; add throws UnsupportedOperationException, because a key alone or a value alone is not a mapping.
That is handy for bulk clean-up and dangerous in an API. A getter that returns flags.keySet() lets any caller clear your internal map. Return Set.copyOf(map.keySet()) for a snapshot or Collections.unmodifiableSet(map.keySet()) for a read-only live view.
values() is not a Set
Values can repeat, so values() is a plain Collection that keeps duplicates. It has no index, so it is not a List either. Common moves:
List<V> list = new ArrayList<>(map.values()); // modifiable copy
List<V> fixed = List.copyOf(map.values()); // unmodifiable copy, no nulls
int total = map.values().stream().mapToInt(Integer::intValue).sum();
values().remove(x) removes only the first mapping with that value; values().removeIf(x::equals) removes all of them.
Copy before you iterate and modify
Changing the map while looping over one of its views throws ConcurrentModificationException. If the loop must call methods that change the map, loop over a copy: for (String k : List.copyOf(map.keySet())).
Order and sequenced maps
The views follow the map: no defined order for HashMap, insertion order for LinkedHashMap, sorted for TreeMap. Since Java 21, LinkedHashMap and TreeMap implement SequencedMap, whose sequencedKeySet() and sequencedValues() add getFirst(), getLast() and reversed().
If you need keys and values together, use entrySet() rather than keySet() plus get.
Example
When to use it
- Returning the list of registered feature flag names from an admin endpoint as a safe copy.
- Summing quantities in a shopping cart map before calculating the order total.
- Restricting a parsed parameter map to allowed names with keySet().retainAll(allowed).
Frequently asked questions
How do I get a list of values from a map in Java?
new ArrayList<>(map.values()) gives a modifiable list; List.copyOf(map.values()) gives an unmodifiable one but throws if any value is null. Both are copies, so later map changes do not affect them.
Does keySet return a copy of the keys?
No. It returns a live view backed by the map. Removing from it removes entries from the map, and new keys added to the map appear in it.
Why does keySet().add throw UnsupportedOperationException?
A key on its own is not a mapping; the map would have no value to store for it. Use map.put(key, value) to add entries.
More examples
Views are live, copies are not
The view picked up tok-3 without being asked again; the copy did not. Adding to a key set fails because there would be no value for the new key.
void main() {
Map<String, String> sessions = new LinkedHashMap<>();
sessions.put("tok-1", "ada");
sessions.put("tok-2", "linus");
Set<String> live = sessions.keySet();
List<String> snapshot = List.copyOf(sessions.keySet());
sessions.put("tok-3", "grace");
IO.println("live: " + live);
IO.println("snapshot: " + snapshot);
try {
live.add("tok-4");
} catch (UnsupportedOperationException e) {
IO.println("add is not supported on a key set");
}
}
// Output:
// live: [tok-1, tok-2, tok-3]
// snapshot: [tok-1, tok-2]
// add is not supported on a key setvalues() keeps duplicates
remove on the values view drops only the first DONE job, while removeIf drops every one. Copying into a TreeSet gives the distinct statuses in sorted order.
void main() {
Map<String, String> jobStatus = new LinkedHashMap<>();
jobStatus.put("j1", "DONE");
jobStatus.put("j2", "RUNNING");
jobStatus.put("j3", "DONE");
jobStatus.put("j4", "FAILED");
IO.println(jobStatus.values());
IO.println(new TreeSet<>(jobStatus.values()));
jobStatus.values().remove("DONE");
IO.println(jobStatus);
jobStatus.values().removeIf("DONE"::equals);
IO.println(jobStatus);
}
// Output:
// [DONE, RUNNING, DONE, FAILED]
// [DONE, FAILED, RUNNING]
// {j2=RUNNING, j3=DONE, j4=FAILED}
// {j2=RUNNING, j4=FAILED}Do not hand out a live key set
Clearing the returned key set emptied the private map behind it. Returning Set.copyOf gives callers the names without any way to change the flags.
class FeatureFlags {
private final Map<String, Boolean> flags =
new TreeMap<>(Map.of("beta-search", true, "new-checkout", false));
Set<String> leakyNames() {
return flags.keySet();
}
Set<String> names() {
return Set.copyOf(flags.keySet());
}
int size() {
return flags.size();
}
}
void main() {
FeatureFlags leaky = new FeatureFlags();
leaky.leakyNames().clear();
IO.println("after clearing the leaky view: " + leaky.size());
FeatureFlags safe = new FeatureFlags();
try {
safe.names().clear();
} catch (UnsupportedOperationException e) {
IO.println("copy is unmodifiable, flags kept: " + safe.size());
}
}
// Output:
// after clearing the leaky view: 0
// copy is unmodifiable, flags kept: 2
Discussion