JavaRush /Java блог /Random UA /Переклад книги. Функіцональне програмування Java. Глава 1...
timurnav
21 рівень

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

Стаття з групи Random UA
Буду радий допомоги в пошуку помилок та покращення якості перекладу. Я перекладаю для прокачування скіла англійської мови, а якщо ви читаєте і шукаєте помилки перекладу, то ви прокачуєтесь ще краще, ніж я. Автор книги пише, що книга припускає наявність великого досвіду роботи з 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 робить можливим написання короткого елегантного коду, в якому буде складніше припуститися помилки, не тільки в простих операціях, але і в усьому нашому додатку.
Старий спосіб
Давайте розглянемо інший приклад. Ми створюємо колекцію з цінами та спробуємо кілька способів обчислити суму всіх цін зі знижкою. Припустимо, нас попросабо підсумовувати всі ціни, значення яких перевищує $20 зі знижкою 10%. Давайте спочатку зробимо це звичним Java способом. Цей код повинен бути добре нам знайомий: спочатку ми створюємо змінну змінну totalOfDiscountedPrices в яку ми збережемо отримане значення. Потім ми запускаємо цикл за колекцією цін, відбираємо ціни, які вищі за $20, отримуємо ціну з урахуванням знижки і додаємо це значення до totalOfDiscountedPrices . Наприкінці ми виводимо суму всіх цін із урахуванням знижки. Нижче те, що виводиться в консоль 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")); 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);
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, але використовуючи якісь додаткові змінні змінні, які потрібно порівняти з чимось з колекції. Потім після вибору певного значення як зробити операції з одним значенням, наприклад мінімум, максимум чи якесь середнє значення? Знову цикли, знову нові змінні. Це нагадує прислів'я, через ліс дерев не видно (в оригіналі використана гра слів пов'язана з ітераціями і означає "За все береться, та не все вдається" - прим. перекладача). Тепер jdk надає внутрішні ітератори для різних операторів: один для спрощення циклічності, другий для прив'язки необхідної залежності результату, один для фільтра значень, один для повернення значень і кілька зручних функцій для отримання мінімуму, максимуму, середніх значень і т.д. На додаток функціональність цих операцій можна дуже просто поєднувати, так що ми можемо поєднувати різні набори з них для реалізації бізнес-логіки з більшою легкістю та меншим обсягом коду. Коли ми закінчимо, код буде простіше розуміти, як він створює логічне рішення в тій послідовності, яка потрібна задачі. Ми розглянемо кілька прикладів такого коду у Розділі 2 і далі у цій книзі.
Застосування алгоритмів
Алгоритми управляють корпоративними програмами. Наприклад, нам необхідно забезпечити операцію, для якої потрібна перевірка повноважень. Ми повинні будемо переконатися, що транзакції проводяться швидко і перевірки пройшли правильно. Такі завдання часто зводяться до звичайнісінького методу, як у поданому нижче лістингу: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); у такому підході є дві проблеми. Перша, це часто призводить до подвоєння докладених зусиль до розробки, що у свою чергу призводить до подорожчання підтримки програми. Друга, дуже просто упустити винятки, які можуть бути кинуті в коді цієї програми, таким чином ставлячи під загрозу виконання транзакції та проходження перевірок. Ми можемо використовувати правильний try-finally блок, але кожного разу, коли хтось буде чіпати цей код, нам буде необхідно повторно переконуватися що логіка коду не була порушена. Інакше ми могли б відмовитися від фабрики і поставити весь код з ніг на голову. Замість того, щоб отримувати транзакції, ми могли б відправити код обробки для добре керованої функції, наприклад, код представлений нижче runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); Ці невеликі зміни призводять до величезної економії. Алгоритм для перевірки статусу та перевірок програми отримує новий рівень абстракції та інкапсулюється за допомогою методу runWithinTransaction() . У цьому методі ми розміщуємо шматок коду, який має виконуватися в контексті транзакції. Нам більше не доведеться турбуватися про те, що ми забули виконати якусь дію і про те, чи в правильному місці ми перехопабо виняток. Функції із застосуванням алгоритмів подбають про це. Більш детально це питання буде розглянуто у Розділі 5.
Розширення алгоритмів
Алгоритми застосовуються все частіше, але щоб вони повноцінно застосовувалися у розробках корпоративних додатків, потрібні способи розширити їх.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ