Generally, use of the new factories is safe for new code, where there is no existing code that depends on behaviors of the existing collections.
There are several reasons the new collection factories aren't drop-in replacements for code that initializes collections using the existing APIs. Obviously immutability is one of the most prominent reasons; if you need to modify the collection later, it obviously can't be immutable! But there are other reasons as well, some of them quite subtle.
For an example of replacement of existing APIs with the new APIs, see JDK-8134373. The review threads are here: Part1 Part2.
Here's a rundown of the issues.
Array Wrapping vs Copying. Sometimes you have an array, e.g. a varargs parameter, and you want to process it as a list. Sometimes Arrays.asList
is the most appropriate thing here, as it's just a wrapper. By contrast, List.of
creates a copy, which might be wasteful. On the other hand, the caller still has a handle to the wrapped array and can modify it, which might be a problem, so sometimes you want to pay the expense of copying it, for example, if you want to keep a reference to the list in an instance variable.
Hashed Collection Iteration Order. The new Set.of
and Map.of
structures randomize their iteration order. The iteration order of HashSet
and HashMap
is undefined, but in practice it turns out to be relatively stable. Code can develop inadvertent dependencies on iteration order. Switching to the new collection factories may expose old code to iteration order dependencies, surfacing latent bugs.
Prohibition of Nulls. The new collections prohibit nulls entirely, whereas the common non-concurrent collections (ArrayList
, HashMap
) allow them.
Serialization Format. The new collections have a different serialization format from the old ones. If the collection is serialized, or it's stored in some other class that's serialized, the serialized output will differ. This might or might not be an issue. But if you expect to interoperate with other systems, this could be a problem. In particular, if you transmit the serialized form of the new collections to a Java 8 JVM, it will fail to deserialize, because the new classes don't exist on Java 8.
Strict Mutator Method Behavior. The new collections are immutable, so of course they throw UnsupportedOperationException
when mutator methods are called. There are some edge cases, however, where behavior is not consistent across all the collections. For example,
Collections.singletonList("").addAll(Collections.emptyList())
does nothing, whereas
List.of("").addAll(Collections.emptyList())
will throw UOE. In general, the new collections and the unmodifiable wrappers are consistently strict in throwing UOE on any call to a mutator method, even if no actual mutation would occur. Other immutable collections, such as those from Collections.empty*
and Collections.singleton*
, will throw UOE only if an actual mutation would occur.
Duplicates. The new Set
and Map
factories reject duplicate elements and keys. This is usually not a problem if you're initializing a collection with a list of constants. Indeed, if a list of constants has a duplicate, it's probably a bug. Where this is potentially an issue is when a caller is allowed to pass in a collection or array (e.g., varags) of elements. If the caller passes in duplicates, the existing APIs would silently omit the duplicates, whereas the new factories will throw IllegalArgumentException
. This is a behavioral change that might impact callers.
None of these issues are fatal problems, but they are behavioral differences that you should be aware of when retrofitting existing code. Unfortunately this means that doing a mass replacement of existing calls with the new collection factories is probably ill-advised. It's probably necessary to do some inspection at each site to assess any potential impact of the behavioral changes.