Search code examples
javacollectionsjava-8java-streamcollectors

Why does Collectors.toList() not guarantee mutability


The implementation of toList() clearly returns an ArrayList which does guarantee mutability:

public static <T>
Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>(ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}

but the JavaDoc of toList reads as:

/**
 * Returns a {@code Collector} that accumulates the input elements into a
 * new {@code List}. There are no guarantees on the type, mutability,
 * serializability, or thread-safety of the {@code List} returned; if more
 * control over the returned {@code List} is required, use {@link #toCollection(Supplier)}.
 *
 * @param <T> the type of the input elements
 * @return a {@code Collector} which collects all the input elements into a
 * {@code List}, in encounter order
 */

Why is mutability not guaranteed? Am I missing cases where the returned collection could be immutable? is it safe to use Collectors.toList() when mutability is required of the returned collection?


Solution

  • Because the authors said so

    Why is mutability not guaranteed?

    Because the designers of the Collectors.toList method chose so.

    The implementation of toList() clearly returns an ArrayList

    That fact is a mere implementation detail.

    And that detail applies only to the one version of the one implementation that you studied. Other versions, and other implementations, may use something other than ArrayList.

    Any implementation is subject to change, within the limits of the contract promised by the documentation. All that matters is the behavior promised by that documentation.

    To quote the Javadoc:

    Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier).

    Seems crystal clear to me. You have been promised that the resulting List may be mutable or may be immutable. You’ve been told not to rely on either being the case.

    Modifiable

    If you want a mutable/modifiable List, 👉🏽 make a new one from the list generated by your collector. Pass the collected list to the constructor of a new list object.

    List< Whatever > list = new ArrayList<>( collectorList ) ;
    

    Or, as the Javadoc above suggests, you can 👉🏽 pass Supplier to render a list implementation of your choice. Call Collectors.toCollection. See section 3.3 of the tutorial on Baeldung.com.

    For example:

    toCollection( ArrayList :: new )
    

    Unmodifiable

    If you want an immutable/unmodifiable list, I suggest 👉🏽 calling List.copyOf.

    List< Whatever > list = List.copyOf( collectorList ) ;
    

    That method promises to return:

    … an unmodifiable List containing the elements of the given Collection, in its iteration order.

    But there is a catch: No nulls allowed. Two alternatives for a list with nulls:


    By the way, Java 21 brought sequenced collections. So the more general interface of List is SequencedCollection.

    SequencedCollection< Whatever > sequence = List.copyOf( collectorList ) ;