JavaRush /Java блог /Архив info.javarush /Перевод книги. Функицональное программирование в Java. Гл...
timurnav
21 уровень

Перевод книги. Функицональное программирование в Java. Глава 1

Статья из группы Архив info.javarush
Буду рад помощи в поиске ошибок и улучшения качества перевода. Я перевожу для прокачки скилла английского языка, а если вы читаете и ищите ошибки перевода, то вы прокачиваетесь еще лучше, чем я. Автор книги пишет, что книга предполагает наличие большого опыта работы с Java, если честно, я сам не особо опытный, но материал книги понял. в книге касаются некоторой теории, которую сложно объяснить на пальцах, если в вики будет достойные статьи, то я буду приводить на них ссылки, но для лучшего понимания рекомендую расчехлять гугл самостоятельно. всем успехов. :) Для тех кому захочется подкорректировать мой перевод, а так же для тех кому он покажется слишком убогим, чтобы читать его на русском, оригинал книги можете скачать тут. Оглавление Глава 1 Hello, Лямбда выражения - читаете в настоящий момент Глава 2 Используем Коллекции - в разработке Глава 3 Строки, Компараторы и Фильтры - в разработке Глава 4 Разработка с лямбда выражениями - в разработке Глава 5 Работа с ресурсами - в разработке Глава 6 Ленимся - в разработке Глава 7 Оптимизируем ресурсы - в разработке Глава 8 Компоновка с лямбда выражениями - в разработке Глава 9 Собираем всё в одну кучу - в разработке

Глава 1 Hello, Лямбда выражения!

Наш Java код готов к замечательным преобразованиям. Каждодневные задачи, которые мы выполняем становятся проще, легче и более выразительными. Новый способ программирования Java использовался на протяжении десятилетий в других языках. С этими изменениями в Java мы можем писать краткий элегантный выразительный код с меньшим количеством ошибок. Мы можем это использовать чтобы легко применять стандарты и реализовывать общие паттерны проектирования с меньшими количеством строк кода. В этой книге мы исследуем функиональный стиль программирования используя прямые примеры задачи из тех, что мы делаем каждый день. Перед тем как мы окунемся в этот элегантный стиль и в этот новый способ разработки программ, давайте разберемся чем он лучше.
Измените свое мышление
Императивный стиль - это то, что Java давала нам с момента создания языка. Этот стиль предполагает, что мы описываем Java каждый шаг того, что мы хотим получить от языка, а затем мы просто смотрим, чтобы эти шаги выполнялись добросовестно. Это работало прекрасно, но это всё же низкий уровень. Код получался слишком многословным, и мы часто хотели язык, который был бы чуть более интеллектуальным. Мы бы могли тогда сказать это декларативно - что мы хотим, а не вникать в то, как это сделать. Хвала разработчикам, Java теперь может помочь нам сделать этого. Давайте рассмотрим на несколько примеров, чтобы понять выгоду и различия между этими подходами.
Привычный путь
Давайте начнем со знакомых основ, чтобы увидеть две парадигмы в действии. Здесь применен императивный способ для поиска Chicago в коллекции cities - листинги в этой книге показывают только фрагменты кода. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); Императивная версия кода noisy(причем тут это слово?) и низкоуровневая, здесь есть несколько изменяемых частей. Сначала мы создаем этот вонючий булевский флаг называемый found и затем мы пробегаем по каждому элементу в коллекции. Если мы находим город который мы ищем, мы устанавливаем флаг на true и прерываем цикл. Наконец мы выводим в консоль результат нашего поиска.
Путь по-лучше
Как наблюдательным Java программистам, достаточно минутного взгляда на этот код чтобы превратить его в что-то более выразительное и простое для чтения, например: System.out.println("Found chicago?:" + cities.contains("Chicago")); Вот это пример декларативного стиля - метод contains() помогает нам получить непосредственно то, что нужно.
Фактические изменения
Эти изменения внесут в наш код в приличное количество улучшений:
  • Нет возни с изменяемыми переменными
  • Итерации цикла скрыты под капотом
  • Меньше беспорядка в коде
  • Большая ясность кода, фокусирует внимание
  • Less impedance; code closely trails the business intent
  • Меньшая вероятность ошибки
  • Проще для понимания и поддержки
За пределами простых случаев
Это был простой пример декларативной функций проверяющей наличие элемента в коллекции, он уже давно применяется в Java. Теперь представьте отсутствие необходимости писать императивный код для более продвинутых операций, как парсинг файлов, работа с базами данных, выполнение запросов на веб сервисы, создание многопоточности и т.д. Теперь Java делает возможным написание краткого элегантного кода в котором будет сложнее допустить ошибку, не только в простых операциях, но и во всем нашем приложении.
Старый способ
Давайте рассмотрим другой пример. Мы создаем коллекцию с ценами и попробуем несколько способов посчитать сумму всех цен со скидкой. final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); Предположим, нас попросили суммировать все цены, значение которых превышает $20, со скидкой 10%. Давайте сначала сделаем это привычным Java способом. BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); Этот код должен быть хорошо нам знаком: сначала мы создаем изменяемую переменную totalOfDiscountedPrices в которую мы сохраним полученное значение. Затем мы запускаем цикл по коллекции цен, отбираем цены, которые выше $20, получаем цену с учетом скидки и добавляем это значение к totalOfDiscountedPrices. В конце мы выводим сумму всех цен с учетом скидки. Ниже то, что выводится в консоль
Total of discounted prices: 67.5
Работает, но код при этом выглядит грязно. Но это не по нашей вине, мы использовали то, что было доступно. Код имеет довольно низкий уровень - он страдает одержимостью примитивами(погуглите, интересная вещь), и он бросает вызов принципу единственной обязанности. Те из нас кто работает дома должен держать такой код далеко от глаз детей, стремящихся стать программистами, это может встревожить их неокрепшие умы, будьте готовы к вопросу "И это то, что тебе приходится делать чтобы выжить?"
Путь по-лучше, еще один
Теперь мы можем сделать лучше, намного лучше. Наш код может напоминать требование спецификации. Это поможет нам уменьшить разрыв между потребностями бизнеса и кодом, который их реализует, еще больше снижая вероятность неправильной интерпретации требований. Вместо того, чтобы создавать переменную и затем неоднократно изменять ее, давайте лучше поработаем на более высоком уровне абстракции, как например в следующем листинге. final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); Давайте прочтем вслух - фильтр цены больше чем 20, замаппить (создать пары "ключ" "значение") по ключу "цена" цену с учетом скидки, и затем добавить их
- комментарий переводчика имеются ввиду, слова возникающие в голове во время чтения кода .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
Код выполняется вместе в той же логичной последовательности, как мы прочитали. Код сократился, но мы использовали целый ряд новых вещей из Java 8. Сначала мы вызвали метод stream() у списка prices. Это открывает дверь к специальному итератору с богатым набором удобных функций, которые мы обсудим позднее. Вместо прямого перебора всех значений списка prices, мы используем несколько специальных методов, таких как filter() и map(). В отличие от методов, которые мы использовали в в Java и JDK, эти методы принимают в качестве параметра в скобках анонимную функцию - лямбда выражение. Позже мы изучим ее детальнее. Вызывая метод reduce() мы вычисляем сумму значений (цена со скидкой) полученных в методе map(). Цикл скрывается так же, как это было при применении метода contains(). Методы filter() и map(), тем не менее еще более сложные. Для каждой цены в списке prices они вызывают переданную лямбда функцию и сохраняют ее в новую коллекцию. Метод reduce() вызывается к этой коллекции для получения итогового результата. Ниже то, что выводится в консоль
Total of discounted prices: 67.5
Изменения
Ниже представлены изменения относительно привычного способа:
  • Код приятен глазу, не загроможден
  • Отсутствуют низкоуровневые операции
  • Проще совершенствование или изменение логики
  • Итерация контролируется библиотекой методов
  • Эффективен, ленивые вычисления циклов
  • Легче распараллелить по потребности
Позже мы обсудим как Java обеспечивает этим улучшения.
Лямбда спешит на помощь :)
Лямбда это функциональный ключ к освобождению нас от неприятностей императивного программирования. Изменением способа нашего программирования, со свежеиспеченными особенностями Java, мы можем писать код не только элегантный и краткий, но так же с меньшей вероятностью ошибок, более эффективный, который легче оптимизировать, совершенствовать и делать мультипоточным.
Крупный выигрыш от Функционального программирования
Стиль функционального программирования имеет большее значение отношения сигнал/шум; мы пишем меньшее количество строк кода, но каждая строка или выражение выполняет бо'льшую функциональность. Мы получили немного от функциональной версии кода, в сравнении с императивной:
  • Мы избежали нежелательных изменений или переназначений переменных, которые являются источником ошибок и усложняют одновременную обработку кода из разных потоков. В императивной версии на протяжении выполнения цикла устанавливаем различные значения переменной totalOfDiscountedPrices. В функциональной версии же, нет явного изменения переменной в коде. Меньшее количество изменений ведет к уменьшению ошибок в коде.
  • Функциональная версия кода проще распараллеливается. Даже если вычисления в методе map() были продолжительными, мы можем запустить их параллельно не опасаясь ничего. Если мы обратимся из разных потоков к коду в императивном стиле, нам необходимо будет беспокоиться об одновременном изменении переменной totalOfDiscountedPrices. В функциональной версии мы получаем доступ к переменной только после того как все изменения будут выполнены, это освобождает нас от заботы о потокобезопасности кода.
  • Код более выразительный. Вместо того, чтобы выполнять код в несколько шагов - создание и инициализация переменной фиктивным значением, выполнение цикла по списку цен, добавление дисконтных цен в переменную и так далее - мы просто просим метод списка map() вернуть другой список с ценами учитывающими скидку и суммируем их.
  • Функциональный стиль более краткий: напотребовалось меньшее количество строк кода, чем в императивной версии. Более компактный код код означает, что его нужно будет меньше писать, меньше читать и проще поддерживать.
  • Функциональная версия кода интуитивно понятна, его легко понимать, стоит только знать его синтаксис. Метод map() применяет переданную функцию (которая вычисляет цену со скидкой) к каждому элементу коллекции и формирует коллекцию с результатом, как мы можем видеть на рисунке ниже.

Картинка Рисунок 1 - метод map применяет переданную функцию к каждому элементу коллекции
С поддержкой лямбда выражений, мы можем полностью использовать всю мощь функционального стиля программирования в Java. Если мы освоим этот стиль, мы сможем создавать более выразительные, более сжатый код с меньшими изменениями и ошибками. Раньше одним из ключевых преимуществ Java была поддержка объектно-ориентированной парадигмы. А функциональный стиль не противоречит ООП. Реальное превосходство в переходе от императивного программирования к декларативному. С Java 8 мы можем довольно эффективно объединить функциональное программирование с объектно-ориентированный стиль. Мы можем продолжить применять ОО стиль для объектов, их области применения, состояния и связей. В дополнение скажем, мы можем моделировать поведение и состояние изменений, бизнес процессов и обработки данных в виде серии наборов функций.
Почему код в функциональном стиле?
Мы видели общую выгоду функционального стиля программирования, но стоит ли изучать этот новый стиль? Будет ли это незначительным изменением в языке или это изменит нашу жизнь? Мы должны получить ответы на эти вопросы, прежде чем потратим наши силы и время. Писать Java код не так уж и сложно, синтаксис языка прост. Нам удобно с хорошо знакомыми библиотеками и API. Что действительно требует от нас усилий для написания и поддержки кода, это типичные Enterprise приложения, в которых мы используем для разработки Java. Нам необходимо убеждаться, что коллеги программисты закрыли соединения с базой данных в правильное время, что они не держат его и не производят транзакций дольше, чем это нужно, что они полностью отлавливают исключения и на правильном уровне, что они ставят и снимают блокировки (локи) должным образом... этот лист можно еще очень долго продолжать. Каждый из перечисленных аргументов в отдельности не имеет веса, но все вместе в сочетании с присущими сложностями реализации, становится подавляющим, трудоемким и сложным в реализации. Что если бы мы могли инкапсулировать эти сложности в крошечные кусочки кода, которые так же смогли бы неплохо управлять ими? Тогда бы мы не тратили постоянно энергию на реализацию стандартов. Это дало бы серьезное превосходство, так что давайте посмотрим как на может помочь функциональный стиль.
Джо спрашивает
Краткий* код означает просто меньшее количество букв кода?
* речь идет о слове concise, которое характеризует функциональный стиль кода с использованием лямбда выражений
В данном контексте подразумевается, что код краток, без мишуры, сводится к прямому воздействию для более эффективной передачи намерений. Это далеко идущие преимущества. Написание кода это как сбор ингредиентов вместе: делая его concise это всё равно что добавить туда соус. Иногда требуется больше усилий для написания такого кода. Меньше кода для чтения, но это делает код более прозрачным. Важно сокращая код оставлять его понятным. Лаконичный код сродни дизайнерским уловкам. Такой код требует меньше танцев с бубном. Это означает, что мы можем быстро реализовать наши идеи и жить дальше, если они заработают и отказаться от них если они не оправдают ожиданий.
Итерации на стероидах
Мы используем итераторы для обработки списков объектов, а так же для работы с Сетами и Мапами. Итераторы, которые мы используем в Java знакомы нам, хоть они и примитивные, но не простые. Они не только занимают некоторое количество строк кода, их так же довольно трудно составлять. Как мы производим итерацию всех элементов коллекции? Мы могли бы использовать цикл for. Как нам выбрать какие-то элементы из коллекции? При помощи того же цикла for, но используя какие-нибудь дополнительные изменяемые переменные, которые нужно сравнить с чем-нибудь из коллекции. Затем после выбора определенного значение как нам произвести операции с одним значением, например минимум, максимум или какое-либо среднее значение? Снова циклы, снова новые переменные. Это напоминает пословицу, из-за леса деревьев не видно (в оригинале использована игра слов связанная с итерациями и означающая "За всё берется, да не всё удается" - прим. переводчика). Теперь jdk предоставляет внутренние итераторы для различных операторов: один для упрощения цикличности, второй для привязки требуемой зависимости результата, один для фильтра выдаваемых значений, один для возврата значений и несколько удобных функций для получения минимума, максимума, средних значений и т.д. В дополнение функциональность этих операции можно очень просто объединять, так что мы можем сочетать различные наборы из них для реализации бизнес-логики с большей легкостью и меньшим объемом кода. Когда мы закончим, код будет проще понимать, как он создает логическое решение в той последовательности, которая требуется в задаче. Мы рассмотрим несколько примеров такого кода в Главе 2 и далее в этой книге.
Применение алгоритмов
Алгоритмы управляют корпоративными приложениями. Например, нам необходимо обеспечить операцию, для которой нужна проверка полномочий. Мы должны будем убедиться в том, что транзакции производятся быстро и проверки прошли верно. Такие задачи часто сводятся к самому обыкновенному методу, как в представленном ниже листинге: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); в таком подходе есть две проблемы. Первая, это часто приводит к удваиванию прилагаемых усилий к разработке, что в свою приводит к удорожанию поддержки приложения. Вторая, очень просто упустить исключения, которые могут быть брошены в коде этого приложения, таким образом, ставя под угрозу выполнение транзакции и прохождение проверок. Мы можем использовать правильный try-finally блок, но каждый раз когда кто-нибудь будет трогать этот код, нам будет необходимо повторно убеждаться что логика кода не была нарушена. Иначе, мы могли бы отказаться от фабрики и поставить весь код с ног на голову. Вместо того, чтобы получать транзакции, мы могли бы отправить код обработки для хорошо управляемой функции, как например, код представленный ниже runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); Эти небольшие изменения приводят к громадной экономии. Алгоритм для проверки статуса и проверок приложения получает новый уровень абстракции и инкапсулируется с помощью метода runWithinTransaction(). Этом методе мы размещаем кусок кода, который должен выполняться в контектсе транзакции. Нам больше не придется беспокоиться о том, что мы забыли выполнить какое-либо действие и о том в правильном ли месте мы перехватили исключение. Функции с применением алгоритмов позаботятся об этом. Более детально этот вопрос будет рассмотрен в Главе 5.
Расширения алгоритмов
Алгоритмы применяются всё чаще, но для того, чтобы они полноценно применялись в разработках корпоративных приложений, требуются способы расширить их.
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
IgorZ Уровень 6
30 августа 2019
перевод книги индуса, интересно
IgorZ Уровень 6
30 августа 2019
Примеры не рабоче