Skip to content

Witaj w Świecie Jutra!

  • Technologie jutra
  • Sprzęt jutra
  • Aplikacje jutra
  • Programowanie
  • Księga Drogi
  • Renowacja
  • Różności
  • Archiwum
  • Autor
  • Home
  • Wszystko
  • Programowanie funkcyjne – Strumienie (1/2)
  • Programowanie
  • Wszystko

Programowanie funkcyjne – Strumienie (1/2)

Jakub Raczkowski 28 marca 2024

Programowanie funkcyjne jako idea, wyrażenia lambda, odniesienia do metody… niby to już wszystko co potrzebne nam będzie do puszczenia silnego, zdrowego strumienia. Tylko dlaczego wydaje mi się jakbym o czymś zapomniał?

Odcinek o lambdzie rozpocząłem smutną historią nieszczęśliwej miłości i matczynego poświęcenia. Jeśli miałbym ochotę na powtórzenie tego formatu w dzisiejszym artykule, to motywem przewodnim byłaby z pewnością:

Zazdrość

W życiu każdego początkującego backendowca przychodzi ten moment kiedy musi dotknąć baz danych. Najpierw robi to dwumetrowym kijem, ostrożnie zza winkla, patrząc co się stanie. Potem znajduje krótszy patyk, kuca obok tabeli i z zaciekawieniem gmera, obserwując rezultaty swoich poczynań. Ostatecznie wkłada w tego SQL-a obie dłonie aż po łokcie i z mieszaniną odrazy oraz fascynacji patrzy jak tłuste zapytania przeciekają mu przez palce. Babra się w kolumnach i rzędach jak małe dziecko w błocie, z niechęcią spoglądając na zegarek, bo w końcu jednak trzeba będzie wrócić do logiki programu.

Firma Oracle, stojąca za sukcesem komercyjnym Javy to również ta sama korporacja co wykupiła MySQLa oraz stworzyła własny system bazodanowy (nazwany po prostu „Oracle„). Aby zrozumieć czym są i jak działają strumienie musimy wpierw wyobrazić sobie jak doszło do ich powstania. W tym celu potrzebny nam będzie „John„. Jest on hipotetycznym pracownikiem Oracle w dziale Database, siedzi sobie wygodnie w swoim boksie i wyszukuje w bazie danych to co mu każą:

  • Przeszukaj bazę komputerów naszych pracowników, znajdź wszystkie, które mają więcej jak 5 lat i pobierz ich lokalizacje – wyślemy tam serwisanta, który dołoży pamięci. A, no chyba, że to Maki, wtedy zaznacz w bazie, że do wymiany.
  • Weź tabele z opiniami testerów o wersji beta 1.1 i 1.2 aplikacji XXX i znajdź negatywne feedbacki, które się powtarzają w obu. Ale tylko w wersji na rynek azjatycki!

John jedną ręką wpisuje zapytanie, drugą trzymając telefon z włączoną aplikacją randkową scrolluje potencjalne kandydatki na żonę. Zez rozbieżny bardzo mu w tym pomaga. Jak również natura jego pracy, która jest łatwa, wygodna i powtarzalna.

Następują przetasowania w firmie. Część działu Database ląduje na bruku, ale nasz John ma szczęście i został „tylko” przesunięty do departamentu programistów Javy. Od teraz będzie pisał logikę oprogramowania.

John z oczywistych powodów nie jest zachwycony nowym otoczeniem, ale szybko się wdraża, poznaje potrzebne biblioteki, rozgryza szczegóły i zaczyna pisać kod. Nie ma już jednak dostępu do produkcyjnej bazy danych, więc testy musi pisać korzystając ze zwykłych javowych kolekcji. Pojawiają się pierwsze ify… else-if… bloki synchronizowane… zmienne ulotne… Proste zapytanie, które w SQL-u zajęłoby mu chwilę, teraz rozrasta się do kilkunastu linijek, gdzie na końcu nie wiadomo co jest czym.

John już rozumie w co się wpakował. Wstaje z fotela i pyta się ogółu:

Tak właśnie żyli programiści Javy do 2014 roku kiedy pojawiła się wersja ósma, a z nią Stream API pozwalające na szybkie i bezpieczne w pracy wielowątkowej przesiewanie kolekcji (i nie tylko).

Nie mam najmniejszych wątpliwości, że motywem przewodnim prac nad Stream API była zazdrość backendowców patrzących na to z jaką gracją specjaliści od baz danych piszą swoje zapytania.

Ci z was, którzy już mieli do czynienia z SQL-em mogą mieć teraz całkiem poprawne rozumowanie tego jak ten system działa. A reszta?

Wyobraźcie sobie woreczek. Nasypiemy do niego jakichś pomieszanych ziarenek, może to być groch z kukurydzą, ważne aby fajnie grzechotało jak się tym bawić. A wiecie, co – dodajmy tam też takich małych żelaznych kulek! I tych styropianowych też!

Woreczek trzeba dobrze zamknąć i zawiesić wysoko, tak aby kot nie dosięgnął łapką, bo jak chwyci to schowa gdzieś pod kanapą i nici z przykładu.

Następnie będą nam potrzebne: jakiś pojemnik (może być drugi worek albo kubek), sitko i scyzoryk.

Teraz od spodu, w tym wiszącym worku wycinamy scyzorykiem dziurę. Towar oczywiście zaczyna się wysypywać, więc podstawiamy sitko, a pod nim pojemnik. Telepiemy sitem i na samym końcu zostają nam tam tylko największe ziarenka: grochu. Cała reszta przeszła przez sitko do kolejnego pojemnika.

Ale to jeszcze nie koniec. Teraz bierzemy porządny magnes i mieszamy nim w tym drugim pojemniku. Cyk – wszystkie metalowe kulki zebrane! Został sam ryż ze styropianem. A zależy nam na ryżu, bo zbliża się pora obiadu.

Wlewamy do garnka wodę, wsypujemy tam zawartość pojemnika i problem sam się rozwiązuje – ryż opada na dno, a leciutki styropian pływa po powierzchni, gotowy do zebrania.

W taki właśnie sposób działają strumienie w Javie

Ważne aby uzmysłowić sobie, że choć metoda może wyglądać jakby tam był tylko jeden strumień, to w rzeczywistości po każdym „filtrowaniu” pojawia się nowy obiekt strumienia i to właśnie na nim wywoływana jest kolejna metoda.

Oryginalny strumień zawierał mieszankę ryżu, grochu, metalu i styropianu, Drugi już był pozbawiony grochu, a ostatni miał tylko styropian i ryż. Ustawiając swoje sitka musimy zatem pamiętać o właściwej kolejności. A same filtry też trzeba jakoś zdefiniować – np. na podstawie wielkości ziaren, albo ich właściwości fizycznych.

List<String> names = List.of("John", "Anna", "Michael");
        return names.stream()
                .filter(t -> t.contains("a"))
                .collect(Collectors.toList());

W poprzednich wpisach z tej serii już kilka razy mówiłem, że całe to programowanie funkcyjne w Javie opiera się na idei interfejsów z jedną metodą abstrakcyjną, która implementowana jest na szybko, tam i w ten sposób jaki akurat jest potrzebny.

To o czym jeszcze nie wspominałem (a czas najwyższy!), to fakt, że te interfejsy zostały już przygotowane przez twórców Javy i nie musimy wymyślać ich od nowa. Lepiej – to właśnie na tych predefiniowanych interfejsach opierają się metody Stream API!

Owe interfejsy funkcyjne różnią się od siebie przyjmowanymi parametrami i zwracanymi typami swoich metod. Na szczęście mają dość intuicyjne nazwy, więc nawet jeśli nie nauczymy się ich na pamięć to i tak będziemy mogli wywnioskować do czego się ich używa, jakie parametry są przyjmowane i co jest zwracane*. Np. interfejs Supplier (and. Dostawca) nie bierze żadnych parametrów, ale coś zwraca, za to jego brat Consumer (ang. Konsument) – przeciwnie – pobiera parametr, ale nic nie daje od siebie.

(*) to akurat łatwe, bo tam wszędzie używa się typów generycznych, więc mamy tylko dwie opcje – albo metoda nie zwraca nic, albo zwraca ten sam typ, który widnieje w klamrach generyka.

Wypunktujmy sobie interfejsy używane w javowych strumieniach:

Comparator<T>

Metoda compare() przyjmuje dwa parametry tego samego typu, ale zawsze zwraca liczbę typu int. Służy do porównywania ze sobą dwóch obiektów na podstawie ich właściwości mierzalnych w liczbach. Możemy też użyć domyślnej metody reversed(), która zwraca odwrotny wynik.

Consumer<T>

W metodzie accept() przyjmuje 1 parametr typu T ale nic nie zwraca. Co ciekawe ten interfejs ma również zaimplementowaną metodę domyślną andThen(), która przyjmuje Consumera i zwraca Consumera, dzięki czemu możemy ustawić co się dzieje z przyjętym parametrem później.

Supplier<T>

Ma tylko jedną metodę get() nieprzyjmującą żadnych parametrów, ale za to zwracającą typ T.

Predicate<T>

Z angielskiego „predicate” oznacza „stwierdzenie”, a to może być albo prawdziwe albo fałszywe. To właśnie jedną z tych wartości zwraca metoda test() tego interfejsu – w oparciu o jeden parametr typu T. Są również metody domyślne: and(), or() albo not() pozwalające łączyć orzeczenia na wzór tego co możemy umieścić w „ifie” używając znaków &&/||/!=.

Function<T,R>

Interfejs „Funkcja” to taka maszynka, że wrzucamy tam jeden obiekt (typu T), a z metody apply() wychodzi drugi – inny (typu R). Możemy też użyć metody domyślnej identity(), że wychodzi dokładnie to co zostało wrzucone. Albo przygotować bardziej złożoną procedurę łącząc kilka funkcji w jedną za pomocą metody compose().

Interfejsy typu Operator

UnaryOperator<T> przyjmuje zmienną typu T, coś z nią robi i wypuszcza rezultat tego samego typu. BinaryOperator<T> to w sumie to samo, ale zamiast jednego, bierze dwa parametry tego samego typu.

Runnable

Runnable zostawiłem na koniec, bo to taki interfejs, który już niby znamy z wielowątkowości, ale jak się okazuje – nadaje się też do operowania strumieniami. Jego metoda run() nic nie pobiera, ale też nic nie zwraca.


Koniec części pierwszej

W początkowym założeniu chciałem, aby cały tekst o strumieniach zmieścił się w jednym artykule, ale w trakcie pisania wykalkulowałem, że albo wpis będzie suchy i z samymi ogólnikami, albo udam, że interfejsy funkcyjne nie są aż takie ważne i pominę ten fragment. Lub też, że treści będzie na tyle dużo, że połowa zainteresowanych się przestraszy i zostawi tekst na później, które nigdy nie nadejdzie (byłem tam, znam to…).

W związku z takim dylematem postanowiłem podzielić całość na dwie części. Dzisiaj odpowiedzieliśmy na pytania: czym jest oraz jak działa strumień w Javie, a także z jakich interfejsów korzystają jego metody.

Natomiast już za kilka dni pojawi się druga część w której zaprezentuję w jaki sposób z takiego strumienia korzystać – jakich metod często się używa i jak zebrać owoce swojego dzieła.

Jak zwykle, dziękuję za uwagę. Kod użyty w tym wpisie znajdziecie na moim GitHubie.

Tags: functional programming

Continue Reading

Previous: Programowanie funkcyjne – Odniesienia do metody
Next: Programowanie funkcyjne – Strumienie (2/2)

Related Stories

Mageege Moon104 – test niskoprofilowego mechanika
  • Sprzęt

Mageege Moon104 – test niskoprofilowego mechanika

11 marca 2025
Przebranżowienie cz.4
  • Programowanie

Przebranżowienie cz.4

27 lutego 2025
Smartfon Jutra
  • Sprzęt

Smartfon Jutra

15 lutego 2025

Ze świata

  • Antyweb
  • Kwantowo
  • Dwóch po dwóch
Kultowe Gears of War wraca w remasterze. Szkoda, że z tymi samymi problemami
Tu nikt nie chciał szukać. Błąd! To może być kolebka życia w kosmosie
Bolą Cię plecy? Naukowcy mówią wprost, co powinieneś robić częściej
Czy Wszechświat to symulacja? Naukowcy znaleźli na to kolejny dowód
Brak ładowarki na wakacjach może Cię sporo kosztować. O tym musisz pamiętać!
Choruje jak Stephen Hawking. Chip w mózgu pozwala mu mówić (i śpiewać!)
Koniec z pestycydami. Rolnicy zastąpią je tym preparatem
Razer Kishi V3 Pro – Mobilny gamepad, który w końcu daje poczuć się jak na konsoli
Finał kultowej serii coraz bliżej. Premiera tuż za rogiem
Garmin chce pozamiatać konkurencję. Inne zegarki stracą racje bytu
Za darmo szkoda nie skorzystać. Warto się pospieszyć
Najpierw cardio, czy siłówka? Naukowcy odpowiadają
Twój smartfon potrafi więcej, niż myślisz! Te funkcje naprawdę ułatwiają życie!
WiZ HDMI Sync Box z taśmą LED - recenzja. Tanio nie znaczy źle
Netflix idzie w zaparte. Mówi że wszystko jest dobrze, ale użytkownicy nie kryją rozczarowania
Flagowy smartfon od Samsunga tuż za rogiem. Czegoś takiego jeszcze nie było
Supernowe zmieniały klimat Ziemi. Dowody są zapisane w lodzie
Klienci T-Mobile wrzuceni pod koła. Hakerzy: mamy dane 64 mln osób
Internet i telefon na firmę - szybko i tanio
To kandydat do filmu roku. Te fakty trzeba znać
Ocalić od zapomnienia
Ostatni kwant
ALH 84001 – meteoryt, o którym mówiono nawet w Białym Domu
HESS zarejestrował kosmiczny elektron o niespotykanej energii [Phys. Rev. Lett.]
Matka ciemnej materii – recenzja biografii “Vera Rubin. Życie”
Satelita, który zerwał się ze smyczy
Wiadomość od Carla Sagana do przyszłych eksploratorów Marsa
Ile najdłużej może trwać zaćmienie Słońca?
Nowa największa liczba pierwsza ma ponad 41 milionów cyfr [GIMPS]
Pulsar 4U 1820-30 wykonuje 716 obrotów na sekundę [AJ]
Ostatni z nas. Zrozumieć The Last of Us – recenzja książki
Nasze podsumowanie Comic Con Baltics 2025
Mała wielka mysz. Glorious Model O 2 mini – recenzja
Tak było na Comic Con Baltics 2025 w Wilnie (Relacja)
O przedsprzedaży Pixel 9a w samochodzie – Odcinek #122
Obejrzeliśmy Ukryty Poziom na raz – No Movie Ci
Premiera Nintendo Switch 2, seriale, i piwniczne artefakty – Odcinek #121
Ludzieee, wy tego używacie? Recenzja klawiatury Trust GXT 867 Acira
Sympatyczny mały flagowiec. Samsung Galaxy S25 – recenzja
Recenzja pionowej myszy Natec Crake 2. Się wziął gruby i nawrócił

To może cię zainteresować:

Mageege Moon104 – test niskoprofilowego mechanika
  • Sprzęt

Mageege Moon104 – test niskoprofilowego mechanika

11 marca 2025
Przebranżowienie cz.4
  • Programowanie

Przebranżowienie cz.4

27 lutego 2025
Smartfon Jutra
  • Sprzęt

Smartfon Jutra

15 lutego 2025
Czym jest Swagger?
  • Programowanie

Czym jest Swagger?

22 lipca 2024
  • Technologie jutra
  • Sprzęt jutra
  • Aplikacje jutra
  • Programowanie
  • Księga Drogi
  • Renowacja
  • Różności
  • Archiwum
  • Autor
Copyright © All rights reserved. | DarkNews by AF themes.