C появлением в Java 8 лямбда-выражений и ряда других подходов, присущих функциональным языкам, выразительная сила, без сомнения, увеличилась, однако появились и далеко не очевидные моменты. Примером может служить работа со списками с применением операций map, filter, reduce.
Эти декларативные функции работы с коллекциями, появились в теории множеств. В качестве аргументов они принимают некоторое множество и функцию преобразования. Функция Map выполняет отображение одного множества на другое путем применения заданной функции к каждому элементу исходного множества. Filter возвращает множество элементов исходного множества, удовлетворяющих заданному условию. Reduce собирает все элементы множества в один по заданному правилу.
Один из плюсов подобных декларативных операций над списками - возможность не только скрыть реализацию обходов списков, но и локализовать ее в одном месте и легко ее модифицировать. Примером может служить распараллеливание операции map.
ExecutorService executorService = Executors.newFixedThreadPool(10);
list.map(e -> executorService.submit(() -> {
Thread.sleep(100);
return e;
})).map(e -> {
Integer ret = null;
try {
ret = e.get();
}
catch (Exception ignore) {}
return ret;
}).filter(e -> e != null).forEach(System.out::println);
Если не обращать на непривычные для Java программиста лямбды все выглядит понятно. Для каждого элемента мы создаем задачу, передаем ее в executorService и получаем объект Future из которого получаем результат с помощью метода get(). Исключаем null элементы и выводим в stdout.
Удивительно здесь то, что все выполняется последовательно. Задачи действительно передается в executorService и даже выполняется в отдельных потоках, но последовательно.
Причина заключается в том что Java 8 переняла от функциональных языков не только лямбда-выражения, но и такую концепцию как ленивые вычисления. Функция map не применяет передаваемую функцию ко всем элементам списка сразу, она лишь возвращает объект Iterable, а отображение происходит по вызову next() этого объекта. В приведенном примере до момента вывода строится цепочка отложенных вычислений, в результате чего они и выполняются последовательно.

Альтернативный пример без ленивых вичислений работает как и предполагалось, выполняя задачи параллельно, однако это требует переопределения функции map на классический последовательный обход списка.
List<Integer> list = new LinkedList<Integer>() {
@Override
public <U> Iterable<U> map(Mapper<? super Integer, ? extends U> mapper) {
List<U> list = new LinkedList<>();
for (Integer e : this) list.add(mapper.map(e));
return list;
}
};