Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Usage of Java 9 collection factories

In the context of the comments and answers given at List.of() or Collections.emptyList() and List.of(...) or Collections.unmodifiableList() I came up with two following rules of thumb (which also apply to Set and Map factories accordingly).

  1. Don't replace all occurrences

Keep using Collections.emptyList() for readability and when e.g. initializing lazy field members like:

class Bean {
  private List<Bean> beans = Collection.emptyList();
  public List<Bean> getBeans() {
    if (beans == Collections.EMPTY_LIST) { beans = new ArrayList<>(); }
    return beans;
  }
}
  1. Use new factories as method argument builders

Use new factories List.of() and variants as quick and less-to-type version, when calling an executable with List parameter(s). Here are my current substitution patterns:

Collections.emptyList()       --> List.of()
Collections.singletonList(a)  --> List.of(a)
Arrays.asList(a, ..., z)      --> List.of(a, ..., z)

In a fictional usage of Collections.indexOfSubList, the following lines

Collections.indexOfSubList(Arrays.asList(1, 2, 3), Collections.emptyList());
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Collections.singletonList(1));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(1));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(2, 3));
Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3));

will read

Collections.indexOfSubList(List.of(1, 2, 3), List.of());
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(2, 3));
Collections.indexOfSubList(List.of(1, 2, 3), List.of(1, 2, 3));

Do you (dis-)agree?

like image 375
Sormuras Avatar asked Nov 30 '16 12:11

Sormuras


People also ask

What is collection factory in Java?

A collection factory method in Java is a static method that provides a simple way of initializing an immutable Collection<E> . Being immutable, no elements can be added to, removed from, or modified inside the Collection<E> after it is initialized.

Which of the factory method is added in collections API in Java 9?

Java 9 Collection library includes static factory methods for List, Set and Map interface. These methods are useful to create small number of collection.

What are the main goals of Java 9?

The main goals for Java 9 are to: Make the Java Standard Edition platform, and the JDK, more navigable to scale down for small computing devices. Improve the overall security and maintain not only the JDK but the Java Implementations in general. Allow overall improved application performance.

Which of the following method is used to map in Java 9?

Java 9 feature – Map.of() method In Java 9, Map. of() was introduced which is a convenient way to create instances of Map interface. It can hold up to 10 key-value pairs.


1 Answers

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.

like image 99
Stuart Marks Avatar answered Oct 11 '22 14:10

Stuart Marks