Sormuras Sormuras - 6 days ago 4
Java Question

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?

Answer

(Im)Mutability

First of all, it is important to note that the collection factories return immutable variants. Unfortunately, this does not show in the type system so you have to track that manually / mentally. This already forbids some replacements that might otherwise be worthwile, so it must become 0. in your list of rules. :)

For example, creating a collection of seed elements that are later modified by other code might look like this:

private final Set<String> commonLetters = initialCommonLetters()

private static Set<String> initialCommonLetters() {
    Set<String> letters = new ArrayList<>();
    letters.add("a");
    letters.add("e");
    return letters;
}

Would be great to simply write commonLetters = Set.of("a", "e"); but this will likely break other code as the returned set is immutable.

Constants

The (im)mutability discussion immediately leads to constants. This was a major reason to introduce them! Gone are the days where you need a static initializer block to create that COMMON_LETTERS constant. This would hence be the place where I would look first for use cases.

Replacing

As you say, there seems to be no reason to start replacing calls to Collections::empty... or Arrays::asList just for the fun of it. What I would do, though, as soon as I start using the new methods in a class I would replace the old variants as well to have the code rely on fewer concepts, making understanding it easier.

Preference

The last argument is also one that could apply to the of() variants in general. While Collections::empty... is clearer about its intent, I slightly tend towards saying always using of, no matter how many arguments you have, offsets that advantage by writing code that, as a whole, uses less concepts.

I see no reason to continue using Arrays::asList.