Почему реализация Stream#toList по умолчанию кажется чрезмерно сложной/неоптимальной?

Глядя на реализацию Stream#toList, я заметил, насколько сложной и неоптимальной она казалась.

Как упоминалось в javadoc чуть выше, эта реализация default не используется в большинстве реализаций Stream, однако, на мой взгляд, могло быть и иначе.

Источники

/**
 * Accumulates the elements of this stream into a {@code List}. The elements in
 * the list will be in this stream's encounter order, if one exists. The returned List
 * is unmodifiable; calls to any mutator method will always cause
 * {@code UnsupportedOperationException} to be thrown. There are no
 * guarantees on the implementation type or serializability of the returned List.
 *
 * <p>The returned instance may be <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>.
 * Callers should make no assumptions about the identity of the returned instances.
 * Identity-sensitive operations on these instances (reference equality ({@code ==}),
 * identity hash code, and synchronization) are unreliable and should be avoided.
 *
 * <p>This is a <a href="package-summary.html#StreamOps">terminal operation</a>.
 *
 * @apiNote If more control over the returned object is required, use
 * {@link Collectors#toCollection(Supplier)}.
 *
 * @implSpec The implementation in this interface returns a List produced as if by the following:
 * <pre>{@code
 * Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())))
 * }</pre>
 *
 * @implNote Most instances of Stream will override this method and provide an implementation
 * that is highly optimized compared to the implementation in this interface.
 *
 * @return a List containing the stream elements
 *
 * @since 16
 */
@SuppressWarnings("unchecked")
default List<T> toList() {
    return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
}

Мое представление о том, что было бы лучше

return (List<T>) Collections.unmodifiableList(Arrays.asList(this.toArray()));

Или даже

return Arrays.asList(this.toArray()));

Предложение IntelliJ

return (List<T>) List.of(this.toArray());

Есть ли веская причина для реализации в исходниках JDK?


person Yassin Hajaj    schedule 04.04.2021    source источник
comment
Это также обсуждалось здесь в комментариях.   -  person Naman    schedule 05.04.2021
comment
Вы неправильно читаете код. Это не та реализация, о которой вы думаете; это реализация по умолчанию, используемая для сторонних реализаций Stream, которые не предоставляют реализацию toList. JDK делает что-то совсем другое; см. ReferencePipeline::toList фактическую реализацию.   -  person Brian Goetz    schedule 05.04.2021


Ответы (1)


Метод toArray может быть реализован для возврата массива, который впоследствии будет видоизменен, что фактически сделает возвращаемый список не неизменным. Вот почему выполняется явное копирование путем создания нового ArrayList.

По сути, это защитная копия.

Это также обсуждалось во время обзора этого API, где Стюарт Маркс пишет:

Как написано, это правда, что реализация по умолчанию действительно выполняет явно избыточные копии, но мы не можем быть уверены, что toArray() действительно возвращает только что созданный массив. Таким образом, мы оборачиваем его с помощью Arrays.asList, а затем копируем с помощью конструктора ArrayList. Это досадно, но необходимо, чтобы избежать ситуаций, когда кто-то может хранить ссылку на внутренний массив списка, что позволяет модифицировать список, который должен быть неизменяемым.

person Jorn Vernee    schedule 04.04.2021
comment
кто еще, если не Йорн... - person Eugene; 05.04.2021
comment
Начиная с версии List.of, нулевые элементы в потоке недопустимы. Фактический toList() терпит их. - person Tagir Valeev; 07.04.2021