109. Dissecting factory methods for collections
Factory methods for collections are a must-have skill. Is very convenient to be able to quickly and effortlessly create and populate unmodifiable/immutable collections before putting them to work.
Factory methods for maps
For instance, before JDK 9, creating an unmodifiable map could be accomplished like this:
Map<Integer, String> map = new HashMap<>();
map.put(1, “Java Coding Problems, First Edition”);
map.put(2, “The Complete Coding Interview Guide in Java”);
map.put(3, “jOOQ Masterclass”);
Map<Integer, String> imap = Collections.unmodifiableMap(map);
This is useful if, at some point in time, you need an unmodifiable map from a modifiable one. Otherwise, you can shortcut this a little bit as follows (this is known as the double-brace initialization technique and as an anti-pattern):
Map<Integer, String> imap = Collections.unmodifiableMap(
new HashMap<Integer, String>() {
{
put(1, “Java Coding Problems, First Edition”);
put(2, “The Complete Coding Interview Guide in Java”);
put(3, “jOOQ Masterclass”);
}
});
If you need to return an unmodifiable/immutable map from a Stream of java.util.Map.entry then here you go:
Map<Integer, String> imap = Stream.of(
entry(1, “Java Coding Problems, First Edition”),
entry(2, “The Complete Coding Interview Guide in Java”),
entry(3, “jOOQ Masterclass”))
.collect(collectingAndThen(
toMap(e -> e.getKey(), e -> e.getValue()),
Collections::unmodifiableMap));
Moreover, let’s not forget the empty and singleton maps (quite useful to return from a method a map instead of null):
Map<Integer, String> imap = Collections.emptyMap();
Map<Integer, String> imap = Collections.singletonMap(
1, “Java Coding Problems, First Edition”);
Starting with JDK 9, we can rely on a more convenient approach for creating unmodifiable/immutable maps thanks to JEP 269: Convenience Factory Methods for Collections. This approach consists of Map.of() which is available from 0 to 10 mappings or, in other words, is overloaded to support 0 to 10 key-value pairs. Here, we use the Map.of() for three mappings:
Map<Integer, String> imap = Map.of(
1, “Java Coding Problems, First Edition”,
2, “The Complete Coding Interview Guide in Java”,
3, “jOOQ Masterclass”
);
Maps created via Map.of() don’t allow null keys or values. Such attempts will end up in a NullPointerException.If you need more than 10 mappings then you can rely on static <K,V> Map<K,V> ofEntries(Entry<? extends K,? extends V>… entries) as follows:
import static java.util.Map.entry;
…
Map<Integer, String> imap2jdk9 = Map.ofEntries(
entry(1, “Java Coding Problems, First Edition”),
entry(2, “The Complete Coding Interview Guide in Java”),
entry(3, “jOOQ Masterclass”)
);
Finally, creating an unmodifiable/immutable map from an existing one can be done via static <K,V> Map<K,V> copyOf(Map<? extends K,? extends V> map):
Map<Integer, String> imap = Map.copyOf(map);
If the given map is unmodifiable then most probably Java will not create a copy and will return an existing instance. In other words, imap == map will return true. If the given map is modifiable then most probably the factory will return a new instance, so imap==map will return false.
Factory methods for lists
Before JDK 9, a modifiable List can be used for creating an unmodifiable List with the same content as follows:
List<String> list = new ArrayList<>();
list.add(“Java Coding Problems, First Edition”);
list.add(“The Complete Coding Interview Guide in Java”);
list.add(“jOOQ Masterclass”);
List<String> ilist = Collections.unmodifiableList(list);
A common approach for creating a List consists of using Arrays.asList():
List<String> ilist = Arrays.asList(
“Java Coding Problems, First Edition”,
“The Complete Coding Interview Guide in Java”,
“jOOQ Masterclass”
);
However, keep in mind that this is a fixed-size list not an unmodifiable/immutable list. In other words, operations that attempt to modify the list size (for instance, ilist.add(…)) will result in UnsupportedOperationException, while operations that modify the current content of the list (for instance, ilist.set(…)) are allowed.If you need to return an unmodifiable/immutable List from a Stream then here you go:
List<String> ilist = Stream.of(
“Java Coding Problems, First Edition”,
“The Complete Coding Interview Guide in Java”,
“jOOQ Masterclass”)
.collect(collectingAndThen(toList(),
Collections::unmodifiableList));
Moreover, creating an empty/singleton list can be done as follows:
List<String> ilist = Collections.emptyList();
List<String> ilist = Collections.singletonList(
“Java Coding Problems, First Edition”);
Starting with JDK 9+, is more convenient to rely on the factory methods List.of() available for 0 to 10 elements (null elements are not allowed):
List<String> ilist = List.of(
“Java Coding Problems, First Edition”,
“The Complete Coding Interview Guide in Java”,
“jOOQ Masterclass”);
If we need a copy of an existing list then rely on List.copyOf():
List<String> ilist = List.copyOf(list);
If the given list is unmodifiable then most probably Java will not create a copy and will return an existing instance. In other words, ilist == list will return true. If the given list is modifiable then most probably the factory will return a new instance, so ilist == list will return false.
Factory methods for sets
Creating Sets follows the same path as Lists. However, pay attention that there is no singletonSet(). For creating a singleton set simply call singleton():
Set<String> iset = Collections.singleton(
“Java Coding Problems, First Edition”);
You can find more examples in the bundled code. You may also be interested in Problem x from Java Coding Problems, First Edition, which covers the unmodifiable versus immutable collections. Moreover, please consider the following problem as well, since it brings more info in this context.