Как обсуждалось в предыдущих обзорах глав, потоки могут значительно сократить наш код до очень кратких выражений. У этой медали есть две стороны. Есть сторона упрощенного кода, которая сразу переходит к сути, а также плотность кода. Многие разработчики, особенно те, у кого нет опыта функционального программирования, могут оказаться на трудной для понимания стороне, когда дело доходит до потоков. Если это объясняет ваши чувства к потокам, продолжайте верить, со временем образец придет к вам. То, что рассматривается в сегодняшней теме, поможет использовать потоки так, как они были задуманы, и, следовательно, не придется бороться с ними.
Как обсуждалось ранее, потоки – это конвейер обработки, это мощная концепция при правильном использовании, при неправильном использовании она может сделать использование более запутанным и трудным для рассуждений, даже скрыть ошибки. Итак, как правильно использовать потоки? Это в значительной степени сводится к использованию чистых функций с потоками. Чистая функция – это функция, которая просто оперирует своими входными данными и выдает выходные данные, она не использует никаких данных извне функции и не влияет ни на что за пределами функции.
Как это часто бывает в нашей практике, давайте рассмотрим пример, который противоречит этому совету. В данном случае программа, которая строит таблицу частот слов из файла.
Mapfrequency = new HashMap<>(); try (Stream words = new Scanner(file).tokens()) { words.forEach(word -> frequency.merge(word.toLowerCase(), 1L, Long::sum)); }
Этот код прост, лаконичен, и он выполняет свою работу, так в чем же может быть проблема? Это не потоковый код. Это итеративный код, использующий stream API. Проще говоря, все, что мы получаем здесь от stream API, – это дополнительная путаница. Функция forEach может быть очень удобной для разработчика, который раньше работал с итеративным кодом, при запуске с streams API при использовании таким образом. Давайте теперь посмотрим, как мы могли бы использовать stream API с кодом потоковой обработки:
Mapfrequency; try(Stream words = new Scanner(file).tokens()) { frequency = words.collect(groupingBy(String::toLowerCase, counting()); }
Итак, в чем же здесь заметные различия? Код в конечном итоге становится короче, но в то же время яснее с определением того, что должно произойти, а не того, как это должно быть сделано. В этом случае соберите слова по их строчной версии и установите значение в качестве количества. Сравните это с версией forEach , которая по своей сути является итеративной и не поддается конвейерной и параллельной обработке, функция foreach должна использоваться просто для отчетности о результатах конвейерной обработки, а не для выполнения фактической обработки бизнес-логики.
Вы обнаружите, что используете функцию collect terminal различными способами, и мы не можем подробно рассмотреть их все в этом сообщении в блоге, но в какой-то момент может быть полезно просмотреть их, чтобы узнать, что возможно. Так же, как и функциональные интерфейсы, вы, скорее всего, не сможете запомнить их все, но знание типов используемых там вещей поможет вам запомнить, какие функции использовать, когда придет время. Существуют методы для создания коллекций всех типов, от списков до карт, наборов и всего, что находится между ними. Существуют специализации практически всех этих функций, которые дают вам больше контроля, если вам это тоже нужно.
Хотя в этой главе основное внимание уделяется конечному узлу в потоке, это не единственное место, где нам нужно беспокоиться о действиях без побочных эффектов. Например, я уже видел код, который принимал поток объектов JPA, а затем обновлял их как часть потока. Хотя этот код выглядел очень хорошо и казался очень чистым, он был крайне неэффективен, поскольку он открывал и закрывал соединения и действовал по одной строке за раз, а не как наборы, подобные наиболее эффективным в системе SQL.
Потребуется некоторая практика, чтобы привыкнуть к обработке потоков надлежащим способом конвейерной обработки, но по мере того, как вы изучите этот режим, станет ясно, какую мощь он предоставляет и чего вы можете достичь, используя этот шаблон.
Оригинал: “https://dev.to/kylec32/effective-java-prefer-side-effect-free-functions-in-streams-5358”