Действительно ли mapToDouble () необходим для суммирования списка с потоками Java 8?


насколько я могу судить, способ суммировать a List<Double> использование Java 8 потоков это:

List<Double> vals = . . . ;
double sum = vals.stream().mapToDouble(Double::doubleValue).sum();

для меня mapToDouble(Double::doubleValue) кажется довольно жестоким - просто своего рода шаблонная "церемония", с которой должны были обходиться лямбды и потоки.

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

double[] vals = . . . ;
double sum = Arrays.stream(vals).sum();

конечно, можно было бы сделать это:

List<Double> vals = . . . ;
double sum = vals.stream().reduce(0.0, (i,j) -> i+j);

но это reduce(....) это намного дольше, чем sum().

Я понимаю, что это связано с тем, как потоки должны быть модифицированы вокруг примитивов без объектов Java, но все же, я что-то упускаю? Есть ли способ сжать автобоксинг, чтобы сделать это короче? Или это просто современное состояние искусства?


Обновление - Ответы Дайджест

вот дайджест ответов ниже. Хотя у меня есть резюме здесь, я призываю читателя внимательно изучить ответы сами по себе в полном объеме.

@dasblinkenlight объясняет, что какой-то распаковки всегда будет необходимо, из-за решений, принятых еще в истории Java, в частности, в том, как были реализованы дженерики и их отношение к примитивам без объектов. Он отмечает, что теоретически компилятор может интуитивно понять распаковку и разрешить более короткий код, но это еще не реализовано.

@Holger показывает решение, которое очень близко к выразительности, о которой я спрашивал:

double sum = vals.stream().reduce(0.0, Double::sum);

Я не знал о новых статический!--9--> метод. Добавлено с 1.8, похоже, предназначено для той самой цели, которую я описывал. Я тоже нашел Double.min() и Double.max(). Забегая вперед, я обязательно буду использовать эту идиому для таких операций на List<Double> и тому подобное.

3 55

3 ответа:

для меня mapToDouble(Double::doubleValue) кажется [что] лямбды и потоки должны были обойтись без.

нужно использовать mapToDouble является следствием решения реализовать дженерики через стирание типа, по существу закрывая дверь на любую возможность использования примитивов внутри дженериков. Это то же самое решение, которое сделало необходимым создать DoubleStream,IntStream и LongStream семейство классов-для обеспечения потоковой распаковки.

есть ли способ сжать автобоксинг, чтобы сделать это короче? Или это просто современное состояние искусства?

к сожалению, не в это время: хотя теоретически компилятор может выяснить, что Stream<Double> можно преобразовать в DoubleStream неявно, таким же образом, что примитивы распакованы, это не было сделано.

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

reduce(....) намного длиннее, чем sum()

согласен, такой подход хуже, чем mapToDouble С точки зрения читабельности.

есть ли способ сжать автобоксинг, чтобы сделать это короче?

Да, есть. Вы можете просто написать:

double sum = vals.stream().mapToDouble(d->d).sum();

это делает распаковку неявной, но, конечно, не добавляет эффективности.

С Listи в штучной упаковке, распаковка является неизбежным. Альтернативным подходом было бы:

double sum = vals.stream().reduce(0.0, Double::sum);

Это не mapToDouble но все же позволяет читать код как "... сумма".

вот еще один способ сделать это. Если вам просто нужна сумма, средняя, минимальная, максимальная и т. д. в списке Double,Integer или Long, вы можете использовать одну из доступных Collectors, например:

List<Double> doubles = Arrays.asList(3.14, 5.15, 4.12, 6.);
System.out.println(
        doubles.stream()
                .collect(Collectors.summingDouble(d -> d))
);

печати 18.41

внимание, что имя метода summingDouble, есть еще один метод, называемый summarizingDouble, который возвращает DoubleSummaryStatistics, содержащий все основные результаты математических операций.