Популярный твит Марио Фуско впечатляюще показывает, в чем главное отличие императивного и функционального подходов к подобным алгоритмам:
Императив против функциональности — разделение интересов pic.twitter.com/G2cC6iBkDJ
— Марио Фуско (@mariofusco) 1 марта 2015 г.
Оба алгоритма делают одно и то же, вероятно, они одинаково быстры и разумны Тем не менее, один из алгоритмов гораздо проще писать и читать, чем другой. Разница заключается в том, что в императивном программировании различные алгоритмические требования распределены по всему блоку кода, тогда как в функциональном программировании каждое требование имеет свою собственную небольшую строку кода. Для сравнения:
- Зеленый: обработка ошибок
- Синий: критерии Стоп
- Красный: операции ввода-вывода
- Желтый: «Бизнес логика»
Функциональное программирование не всегда превосходит императивное программирование, как показано в других примерах в блоге jOOQ :
- Как использовать функциональное программирование Java 8 для генерации буквенной последовательности
- Как использовать потоки Java 8 для быстрой замены элементов в списке
Но вот пример из переполнения стека пользователем Aurora_Titanium , где разница столь же очевидна, как и в примере с Марио Фуско:
Вычисление повторяющихся значений в массиве
Идея состоит в том, чтобы вычислить сумму всех этих значений, которые дублируются в наборе значений. Например, следующий массив:
1
|
int [] list = new int []{ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 8 , 8 , 9 , 10 }; |
… должен дать в результате что-то вроде:
1
|
Duplicate: 8. Sum of all duplicate values: 24 |
Императивный подход
Один из ответов пользователя Volkan Ozkan использует императивный подход и вычисляет сумму следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
int [] array = new int [] { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 8 , 8 , 9 , 10 }; int sum = 0 ; for ( int j = 0 ; j < array.length; j++) { for ( int k = j + 1 ; k < array.length; k++) { if (k != j && array[k] == array[j]) { sum = sum + array[k]; System.out.println( "Duplicate found: " + array[k] + " " + "Sum of the duplicate value is " + sum); } } } |
Подход работает только для отсортированных массивов, где дубликаты появляются сразу после друг друга. В этом случае, однако, это, вероятно, оптимальное решение с точки зрения производительности, если производительность действительно имеет значение для этого алгоритма .
Функциональный подход
Если для вас приемлемо небольшое снижение производительности (боксирование, сбор их в карты), и, возможно, это так, вы можете заменить вышеприведенный трудно читаемый код следующим фрагментом функциональной логики в стиле Java-8, которая гораздо яснее рассказывает, что делает:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
int [] array = new int [] { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 8 , 8 , 9 , 10 }; IntStream.of(array) .boxed() .collect(groupingBy(i -> i)) .entrySet() .stream() .filter(e -> e.getValue().size() > 1 ) .forEach(e -> { System.out.println( "Duplicates found for : " + e.getKey() + " their sum being : " + e.getValue() .stream() .collect(summingInt(i -> i))); }); |
или с объяснениями:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
int [] array = new int [] { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 8 , 8 , 9 , 10 }; // Create a Stream<Integer> from your data IntStream.of(array) .boxed() // Group values into a Map<Integer, List<Integer>> .collect(groupingBy(i -> i)) // Filter out those map values that have only // 1 element in their group .entrySet() .stream() .filter(e -> e.getValue().size() > 1 ) // Print the sum for the remaining groups .forEach(e -> { System.out.println( "Duplicates found for : " + e.getKey() + " their sum being : " + e.getValue() .stream() .collect(summingInt(i -> i))); }); |
(обратите внимание, что функциональный подход вычисляет суммы для каждого дублирующего значения, а не общую сумму, как императивный подход. Из первоначального вопроса это требование было не очень ясным)
Как мы уже говорили в предыдущей статье в нашем блоге, мощь функционального программирования с помощью API, такого как Java 8 Stream API, заключается в том, что мы приближаемся к выразительной силе декларативного программирования в стиле SQL. Мы больше не заботимся о запоминании индексов отдельных массивов и о том, как их вычислять и сохранять промежуточные результаты в некоторых буферах. Теперь мы можем сосредоточиться на действительно интересной логике, такой как: «что такое дубликат?» или «какая сумма меня интересует?»
Прочтите о том, как SQL сравнивается с потоками Java 8: общие предложения SQL и их эквиваленты в потоках Java 8
Ссылка: | Сравнение императивных и функциональных алгоритмов в Java 8 от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и AND JOOQ . |