JavaRush /Blog Java /Random-PL /Tłumaczenie książki. Programowanie funkcjonalne w Javie. ...
timurnav
Poziom 21

Tłumaczenie książki. Programowanie funkcjonalne w Javie. Rozdział 1

Opublikowano w grupie Random-PL
Chętnie pomogę znaleźć błędy i poprawić jakość tłumaczenia. Tłumaczę, aby poprawić swoją znajomość języka angielskiego, a jeśli czytasz i szukasz błędów w tłumaczeniu, poprawisz się jeszcze lepiej ode mnie. Autor książki pisze, że książka zakłada duże doświadczenie w pracy z Javą, szczerze mówiąc, sam nie jestem szczególnie doświadczony, ale zrozumiałem materiał zawarty w książce. Książka omawia pewną teorię, którą ciężko wytłumaczyć na palcach.Jeśli na wiki są jakieś przyzwoite artykuły to podam linki do nich, ale dla lepszego zrozumienia radzę samemu poszukać w Google. powodzenia wszystkim. :) Dla tych, którzy chcą poprawić moje tłumaczenie, a także dla tych, którzy uważają, że jest zbyt słabe, aby przeczytać je w języku rosyjskim, możesz pobrać oryginalną książkę tutaj . Spis treści Rozdział 1 Witaj, wyrażenia Lambda – obecnie czytam Rozdział 2 Używanie kolekcji – w fazie rozwoju Rozdział 3 Ciągi znaków, komparatory i filtry – w fazie rozwoju Rozdział 4 Programowanie za pomocą wyrażeń Lambda – w fazie rozwoju Rozdział 5 Praca z zasobami – w fazie rozwoju Rozdział 6 Lenistwo – w fazie rozwoju development Rozdział 7 Optymalizacja zasobów – w development Rozdział 8 Układ z wyrażeniami lambda – w development Rozdział 9 Składanie tego wszystkiego w całość – w development

Rozdział 1 Witajcie, wyrażenia lambda!

Nasz kod Java jest gotowy na niezwykłe transformacje. Codzienne zadania, które wykonujemy, stają się prostsze, łatwiejsze i bardziej wyraziste. Nowy sposób programowania w Javie jest stosowany od kilkudziesięciu lat w innych językach. Dzięki tym zmianom w Javie możemy pisać zwięzły, elegancki i wyrazisty kod z mniejszą liczbą błędów. Możemy to wykorzystać do łatwego stosowania standardów i wdrażania powszechnych wzorców projektowych przy mniejszej liczbie linii kodu. W tej książce badamy funkcjonalny styl programowania na prostych przykładach problemów, z którymi spotykamy się na co dzień. Zanim zagłębimy się w ten elegancki styl i nowy sposób tworzenia oprogramowania, zobaczmy, dlaczego jest on lepszy.
Zmień swoje myślenie
Styl rozkazujący jest tym, co Java dała nam od początków tego języka. Ten styl sugeruje, że opisujemy Javie każdy krok tego, co chcemy, aby język zrobił, a następnie po prostu upewniamy się, że te kroki są wiernie wykonywane. To zadziałało świetnie, ale nadal jest na niskim poziomie. Kod okazał się zbyt szczegółowy i często potrzebowaliśmy języka, który byłby nieco bardziej inteligentny. Moglibyśmy wtedy powiedzieć deklaratywnie – czego chcemy, i nie zagłębiać się w to, jak to zrobić. Dzięki programistom Java może nam teraz w tym pomóc. Przyjrzyjmy się kilku przykładom, aby zrozumieć korzyści i różnice między tymi podejściami.
Zwykły sposób
Zacznijmy od znanych podstaw, aby zobaczyć oba paradygmaty w działaniu. Wykorzystuje to metodę imperatywną do wyszukiwania Chicago w zbiorze miast — wykazy w tej książce pokazują jedynie fragmenty kodu. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); Wersja imperatywna kodu jest zaszumiona (co to słowo ma z tym wspólnego?) i niskopoziomowa, istnieje kilka zmiennych części. Najpierw tworzymy tę śmierdzącą flagę logiczną o nazwie found , a następnie iterujemy po każdym elemencie kolekcji. Jeśli znajdziemy miasto, którego szukamy, ustawiamy flagę na true i przerywamy pętlę. Na koniec wypisujemy wynik naszego wyszukiwania na konsolę.
Jest lepszy sposób
Jako spostrzegawczy programiści Java rzut oka na ten kod może zamienić go w coś bardziej wyrazistego i łatwiejszego do odczytania, na przykład: System.out.println("Found chicago?:" + cities.contains("Chicago")); Oto przykład stylu deklaratywnego — metoda zawiera() pomaga nam dostać się bezpośrednio do tego, czego potrzebujemy.
Rzeczywiste zmiany
Zmiany te wprowadzą przyzwoitą ilość ulepszeń do naszego kodu:
  • Żadnych kłopotów ze zmiennymi zmiennymi
  • Iteracje pętli ukryte są pod maską
  • Mniej bałaganu w kodzie
  • Większa przejrzystość kodu, skupia uwagę
  • Mniejsza impedancja; kod ściśle podąża za intencją biznesową
  • Mniejsze ryzyko błędu
  • Łatwiejsze do zrozumienia i wsparcia
Poza prostymi przypadkami
To był prosty przykład funkcji deklaratywnej sprawdzającej obecność elementu w kolekcji; była ona używana od dawna w Javie. Teraz wyobraź sobie, że nie musisz pisać kodu imperatywnego dla bardziej zaawansowanych operacji, takich jak analizowanie plików, praca z bazami danych, wysyłanie żądań do usług internetowych, tworzenie wielowątkowości itp. Java umożliwia teraz pisanie zwięzłego, eleganckiego kodu, który utrudnia popełnianie błędów nie tylko w prostych operacjach, ale w całej naszej aplikacji.
Stary sposób
Spójrzmy na inny przykład. Tworzymy kolekcję z cenami i spróbujemy na kilka sposobów obliczyć sumę wszystkich obniżonych cen. Załóżmy, że poproszono nas o zsumowanie wszystkich cen, których wartość przekracza 20 dolarów, z 10% rabatem. Najpierw zróbmy to w zwykły sposób w Javie. Ten kod powinien być nam dobrze znany: najpierw tworzymy zmienną totalOfDiscountedPrices , w której będziemy przechowywać wynikową wartość. Następnie przeglądamy kolekcję cen, wybieramy ceny powyżej 20 USD, uzyskujemy obniżoną cenę i dodajemy tę wartość do totalOfDiscountedPrices . Na koniec wyświetlamy sumę wszystkich cen uwzględniających rabat. Poniżej znajduje się to, co jest wysyłane do konsoli 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);
Suma obniżonych cen: 67,5
Działa, ale kod wygląda na bałagan. Ale to nie nasza wina, wykorzystaliśmy to, co było pod ręką. Kod jest na dość niskim poziomie - cierpi na obsesję na punkcie prymitywów (wpisz w Google, ciekawe rzeczy) i jest to sprzeczne z zasadą pojedynczej odpowiedzialności . Ci z nas, którzy pracują w domu, powinni trzymać taki kod z dala od oczu dzieci aspirujących do zostania programistami, może to zaniepokoić ich kruche umysły, być przygotowanym na pytanie: „Czy to właśnie musisz zrobić, aby przeżyć?”
Jest lepszy sposób, inny
Teraz możemy działać lepiej, znacznie lepiej. Nasz kod może przypominać wymagania specyfikacji. Pomoże nam to zmniejszyć rozbieżność między potrzebami biznesowymi a kodem, który je implementuje, jeszcze bardziej zmniejszając prawdopodobieństwo błędnej interpretacji wymagań. Zamiast tworzyć zmienną, a następnie wielokrotnie ją zmieniać, pracujmy na wyższym poziomie abstrakcji, jak na poniższym zestawieniu. 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); Przeczytajmy to na głos - filtr ceny jest większy niż 20, zmapuj (utwórz pary „klucz” „wartość”) za pomocą klucza „cena”, cenę zawierającą rabat, a następnie dodaj je
- komentarz tłumacza oznacza słowa, które pojawiają się w Twojej głowie podczas czytania kodu .filter(cena -> cena.compareTo(BigDecimal.valueOf(20)) > 0)
Kod jest wykonywany razem w tej samej logicznej kolejności, jaką przeczytaliśmy. Kod został skrócony, ale wykorzystaliśmy całą masę nowości z Javy 8. Najpierw wywołaliśmy metodę stream() na cenniku . Otwiera to drzwi do niestandardowego iteratora z bogatym zestawem wygodnych funkcji, które omówimy później. Zamiast bezpośrednio przeglądać wszystkie wartości w cenniku , używamy kilku specjalnych metod, takich jak filter() i map() . W przeciwieństwie do metod, których używaliśmy w Javie i JDK, metody te przyjmują funkcję anonimową – wyrażenie lambda – jako parametr w nawiasach. Przeanalizujemy to bardziej szczegółowo później. Wywołując metodę redukcji() obliczamy sumę wartości (ceny po obniżce) uzyskanych w metodzie map() . Pętla jest ukryta w taki sam sposób, jak przy użyciu metody zawiera() . Metody filter() i map() są jednak jeszcze bardziej złożone. Dla każdej ceny w cenniku wywołują przekazaną funkcję lambda i zapisują ją w nowej kolekcji. W tej kolekcji wywoływana jest metoda redukcji() w celu uzyskania końcowego wyniku. Poniżej znajduje się to, co jest wysyłane do konsoli
Suma obniżonych cen: 67,5
Zmiany
Poniżej znajdują się zmiany w stosunku do zwykłej metody:
  • Kod jest przyjemny dla oka i nie jest zaśmiecony.
  • Żadnych operacji na niskim poziomie
  • Łatwiej ulepszyć lub zmienić logikę
  • Iteracją steruje biblioteka metod
  • Wydajna, leniwa ocena pętli
  • W razie potrzeby łatwiej jest pracować równolegle
Omówimy później, w jaki sposób Java zapewnia te ulepszenia.
Lambda na ratunek :)
Lambda jest kluczem funkcjonalnym uwalniającym nas od kłopotów związanych z programowaniem imperatywnym. Zmieniając sposób programowania, dzięki najnowszym funkcjom Java, możemy pisać kod, który jest nie tylko elegancki i zwięzły, ale także mniej podatny na błędy, wydajniejszy i łatwiejszy w optymalizacji, ulepszaniu i tworzeniu wielowątkowości.
Wygraj dużo dzięki programowaniu funkcjonalnemu
Funkcjonalny styl programowania ma wyższy stosunek sygnału do szumu ; Piszemy mniej linii kodu, ale każda linia lub wyrażenie spełnia większą funkcjonalność. Niewiele zyskaliśmy z funkcjonalnej wersji kodu w porównaniu z imperatywem:
  • Uniknęliśmy niepożądanych zmian czy ponownego przypisania zmiennych, które są źródłem błędów i utrudniają jednoczesne przetwarzanie kodu z różnych wątków. W wersji imperatywnej w całej pętli ustawiamy różne wartości zmiennej totalOfDiscountedPrices . W wersji funkcjonalnej nie ma wyraźnej zmiany zmiennej w kodzie. Mniej zmian prowadzi do mniejszej liczby błędów w kodzie.
  • Funkcjonalną wersję kodu łatwiej jest zrównoleglić. Nawet jeśli obliczenia w metodzie map() były długotrwałe, możemy je przeprowadzić równolegle bez obaw. Jeśli uzyskujemy dostęp do kodu w stylu imperatywnym z różnych wątków, będziemy musieli się martwić o jednoczesną zmianę zmiennej totalOfDiscountedPrices . W wersji funkcjonalnej dostęp do zmiennej mamy dopiero po dokonaniu wszelkich zmian, uwalnia to nas od obaw o bezpieczeństwo wątków kodu.
  • Kod jest bardziej wyrazisty. Zamiast wykonywać kod w kilku krokach — tworzyć i inicjować zmienną o wartości fikcyjnej, przeglądać listę cen, dodawać do zmiennej ceny z rabatem itd. — po prostu prosimy metodę map() listy o zwrócenie kolejnej listy obniżonych cen i zsumuj je .
  • Styl funkcjonalny jest bardziej zwięzły: wymaganych jest mniej wierszy kodu niż wersja imperatywna. Bardziej zwarty kod oznacza mniej pisania, mniej czytania i łatwiejszy w utrzymaniu.
  • Funkcjonalna wersja kodu jest intuicyjna i łatwa do zrozumienia, jeśli poznasz jej składnię. Metoda map() stosuje przekazaną funkcję (która oblicza obniżoną cenę) do każdego elementu kolekcji i tworzy kolekcję z wynikiem, jak widać na obrazku poniżej.

Rysunek Rysunek 1 – metoda map stosuje przekazaną funkcję do każdego elementu kolekcji
Dzięki wsparciu wyrażeń lambda możemy w pełni wykorzystać siłę funkcjonalnego stylu programowania w Javie. Jeśli opanujemy ten styl, możemy stworzyć bardziej wyrazisty, zwięzły kod z mniejszą liczbą zmian i błędów. Wcześniej jedną z kluczowych zalet Javy było wsparcie dla paradygmatu obiektowego. A styl funkcjonalny nie jest sprzeczny z OOP. Prawdziwa doskonałość w przejściu od programowania imperatywnego do deklaratywnego. Dzięki Javie 8 możemy dość efektywnie łączyć programowanie funkcjonalne ze stylem obiektowym. Możemy nadal stosować styl OO do obiektów, ich zakresu, stanu i relacji. Dodatkowo możemy modelować zachowanie i stan zmian, procesów biznesowych i przetwarzania danych w postaci serii zestawów funkcyjnych.
Dlaczego kodować w stylu funkcjonalnym?
Widzieliśmy ogólne zalety funkcjonalnego stylu programowania, ale czy warto się uczyć tego nowego stylu? Czy będzie to niewielka zmiana w języku, czy może odmieni nasze życie? Musimy uzyskać odpowiedzi na te pytania, zanim będziemy marnować czas i energię. Pisanie kodu w Javie nie jest takie trudne, składnia języka jest prosta. Dobrze radzimy sobie ze znanymi bibliotekami i interfejsami API. To, co naprawdę wymaga od nas włożenia wysiłku w pisanie i utrzymywanie kodu, to typowe aplikacje dla przedsiębiorstw, w których do programowania używamy języka Java. Musimy zadbać o to, aby inni programiści zamykali połączenia z bazą danych w odpowiednim momencie, aby jej nie przetrzymywali i nie wykonywali transakcji dłużej niż to konieczne, aby w pełni i na właściwym poziomie wychwytywali wyjątki, aby prawidłowo stosowali i zwalniali blokady. ...ten arkusz można kontynuować przez bardzo długi czas. Każdy z powyższych argumentów sam w sobie nie ma znaczenia, ale w połączeniu z nieodłączną złożonością implementacji staje się przytłaczający, czasochłonny i trudny do wdrożenia. Co by było, gdybyśmy mogli zawrzeć te złożoności w małych fragmentach kodu, które również dobrze by nimi zarządzały? Wtedy nie poświęcalibyśmy stale energii na wdrażanie standardów. Dałoby to poważną przewagę, więc przyjrzyjmy się, jak funkcjonalny styl może pomóc.
– pyta Jo
Czy krótki* kod oznacza po prostu mniej liter kodu?
* mówimy o słowie concise , które charakteryzuje funkcjonalny styl kodu wykorzystujący wyrażenia lambda
W tym kontekście kod ma być zwięzły, bez zbędnych bajerów i ograniczać się do bezpośredniego oddziaływania, aby skuteczniej przekazywać intencje. To dalekosiężne korzyści. Pisanie kodu jest jak łączenie składników: zwięzłość jest jak dodawanie do niego sosu. Czasami napisanie takiego kodu wymaga więcej wysiłku. Mniej kodu do odczytania, ale dzięki temu kod jest bardziej przejrzysty. Ważne jest, aby podczas skracania kodu zachować jego przejrzystość. Zwięzły kod przypomina sztuczki projektowe. Ten kod wymaga mniej tańca z tamburynem. Oznacza to, że możemy szybko wdrożyć nasze pomysły i iść dalej, jeśli się sprawdzają, oraz porzucić je, jeśli nie spełniają oczekiwań.
Iteracje na sterydach
Iteratorów używamy do przetwarzania list obiektów, a także do pracy z zestawami i mapami. Iteratory, których używamy w Javie, są nam znane i chociaż są prymitywne, nie są proste. Nie tylko zajmują wiele linii kodu, ale są również dość trudne do napisania. Jak iterować po wszystkich elementach kolekcji? Przydałaby nam się pętla for. Jak wybrać wybrane elementy z kolekcji? Używając tej samej pętli for, ale używając dodatkowych zmiennych, które należy porównać z czymś z kolekcji. Następnie, po wybraniu konkretnej wartości, jak wykonać operacje na pojedynczej wartości, takiej jak minimalna, maksymalna lub jakaś wartość średnia? Znowu cykle, znowu nowe zmienne. Przypomina to przysłowie, że przez las nie widać drzew (w oryginale zastosowano grę słów związaną z iteracjami i oznacza „Wszystko się podejmuje, ale nie wszystko się udaje” – przyp. tłumacza). jdk udostępnia teraz wewnętrzne iteratory dla różnych instrukcji: jeden upraszczający pętlę, drugi wiążący wymaganą zależność wyników, drugi filtrujący wartości wyjściowe, trzeci zwracający wartości i kilka wygodnych funkcji do uzyskiwania wartości min, max, średnich itp. Dodatkowo funkcjonalność tych operacji można bardzo łatwo łączyć, dzięki czemu możemy łączyć różne ich zestawy w celu implementacji logiki biznesowej z większą łatwością i przy mniejszym kodzie. Kiedy skończymy, kod będzie łatwiejszy do zrozumienia, ponieważ tworzy logiczne rozwiązanie w kolejności wymaganej przez problem. Przykładom takiego kodu przyjrzymy się w rozdziale 2 i w dalszej części tej książki.
Zastosowanie algorytmów
Algorytmy napędzają aplikacje korporacyjne. Na przykład musimy zapewnić operację wymagającą sprawdzania uprawnień. Będziemy musieli zadbać o szybką realizację transakcji i prawidłowe przeprowadzenie kontroli. Takie zadania często sprowadza się do bardzo zwyczajnej metody, jak pokazano na poniższej liście: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); Z takim podejściem wiążą się dwa problemy. Po pierwsze, często prowadzi to do podwojenia wysiłku rozwojowego, co z kolei prowadzi do wzrostu kosztów utrzymania aplikacji. Po drugie, bardzo łatwo jest przeoczyć wyjątki, które mogą zostać zgłoszone w kodzie tej aplikacji, zagrażając w ten sposób realizacji transakcji i przejściu kontroli. Możemy użyć odpowiedniego bloku try-finally, ale za każdym razem, gdy ktoś dotknie tego kodu, będziemy musieli ponownie sprawdzić, czy logika kodu nie została złamana. W przeciwnym razie moglibyśmy porzucić fabrykę i wywrócić cały kod do góry nogami. Zamiast otrzymywać transakcje, moglibyśmy wysłać kod przetwarzający do dobrze zarządzanej funkcji, takiej jak poniższy kod.Te runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); małe zmiany dają ogromne oszczędności. Algorytm sprawdzania statusu i sprawdzania aplikacji zyskał nowy poziom abstrakcji i jest hermetyzowany przy użyciu metody runWithinTransaction() . W tej metodzie umieszczamy fragment kodu, który powinien zostać wykonany w kontekście transakcji. Nie musimy się już martwić, że o czymś zapomnimy lub czy wyłapaliśmy wyjątek w odpowiednim miejscu. Zajmują się tym funkcje algorytmiczne. Zagadnienie to zostanie omówione szerzej w rozdziale 5.
Rozszerzenia algorytmów
Algorytmy są stosowane coraz częściej, jednak aby można je było w pełni wykorzystać w tworzeniu aplikacji korporacyjnych, potrzebne są sposoby ich rozszerzania.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION