Почему Stream не имеет метода toList()?

При использовании потоков Java 8 довольно часто можно взять список, создать поток из него, выполнить бизнес и преобразовать его. Что-то вроде:

 Stream.of(-2,1,2,-5)
        .filter(n -> n > 0)
        .map(n -> n * n)
        .collect(Collectors.toList());

Почему нет короткого/удобного метода для части ".collect(Collectors.toList())"? В интерфейсе Stream есть метод преобразования результатов в массив с именем toArray(), почему отсутствует toList()?

IMHO, преобразование результата в список чаще, чем для массива. Я могу жить с этим, но это довольно раздражает, чтобы называть это уродство.

Любые идеи?

Ответ 1

Недавно я написал небольшую библиотеку под названием StreamEx, которая расширяет потоки Java и предоставляет этот точный метод среди многих других функций:

StreamEx.of(-2,1,2,-5)
    .filter(n -> n > 0)
    .map(n -> n * n)
    .toList();

Также доступны toSet(), toCollection (Поставщик), join(), groupingBy() и другие методы быстрого доступа.

Ответ 2

Что касается "почему", я считаю, что в комментариях есть довольно много аргументов. Однако я согласен с вами в том, что это довольно раздражает, если у вас нет метода toList(). То же самое происходит с методом toIterable().

Итак, я покажу вам трюк, который позволит вам использовать эти два метода в любом случае. К счастью, Java очень гибкий и позволяет делать всевозможные интересные вещи. Около 10 лет назад я прочитал эту статью, в которой описывается остроумный трюк, чтобы "подключить" методы к любому данному интерфейсу. Трюк состоит в использовании прокси для адаптации интерфейса, который не имеет методов, которые вы хотите. На протяжении многих лет я обнаружил, что у нее есть все преимущества адаптера, в то время как у него отсутствуют все его недостатки. Это то, что я называю большой сделкой.

Вот пример кода, просто чтобы показать идею:

public class Streams {

    public interface EnhancedStream<T>
        extends Stream<T> {

        List<T> toList();

        Iterable<T> toIterable();
    }

    @SuppressWarnings("unchecked")
    public static <T> EnhancedStream<T> enhance(Stream<T> stream) {

        return (EnhancedStream<T>) Proxy.newProxyInstance(
            EnhancedStream.class.getClassLoader(),
            new Class<?>[] {EnhancedStream.class}, 
            (proxy, method, args) -> {

            if ("toList".equals(method.getName())) {

                return stream.collect(Collectors.toList());

            } else if ("toIterable".equals(method.getName())) {

                return (Iterable<T>) stream::iterator;

            } else {
                // invoke method on the actual stream
                return method.invoke(stream, args);
            }
        });
    }

    public static void main(String[] args) {

        Stream<Integer> stream1 = Stream.of(-2, 1, 2, -5).
            filter(n -> n > 0).map(n -> n * n);
        List<Integer> list = Streams.enhance(stream1).toList();
        System.out.println(list); // [1, 4]

        Stream<Integer> stream2 = Stream.of(-2, 1, 2, -5).
            filter(n -> n > 0).map(n -> n * n);
        Iterable<Integer> iterable = Streams.enhance(stream2).toIterable();
        iterable.forEach(System.out::println); // 1
                                               // 4
    }
}

Идея состоит в использовании интерфейса EnhancedStream, который расширяет интерфейс Java Stream, определяя методы, которые вы хотите добавить. Затем динамический прокси-сервер реализует этот расширенный интерфейс, делегируя оригинальные методы Stream для адаптируемого фактического потока, в то время как он просто предоставляет встроенную реализацию новым методам (те, которые не определены в Stream).

Этот прокси доступен с помощью статического метода, который прозрачно выполняет все проксирование.

Обратите внимание, что я не утверждаю, что это окончательное решение. Вместо этого это просто пример, который может быть значительно улучшен, т.е. Для каждого метода Stream, который возвращает другой Stream, вы также можете вернуть прокси-сервер для этого. Это позволит привязать EnhancedStream (вам нужно переопределить эти методы в интерфейсе EnhancedStream, чтобы они возвращали тип возвращаемого ковариатора EnhancedStream). Кроме того, отсутствует правильная обработка исключений, а также более надежный код, чтобы решить, следует ли делегировать выполнение методов исходному потоку или нет.

Ответ 3

Вот простой класс-помощник, который делает это проще:

public class Li {
    public static <T> List<T> st(final Stream<T> stream) {
        return stream.collect(Collectors.toList());
    }
}

Пример использования:

List<String> lowered = Li.st(Stream.of("HELLO", "WORLD").map(String::toLowerCase));

Ответ 4

Это довольно удобно:

resultList = 
    originalList.stream().
    filter(ball -> ball.isRed()).
    collect(Collectors.toCollection(ArrayList::new));