Przejdź do głównej zawartości

Immutable collections - Java

Mierzenie się z kolekcjami i poprawnym ich wykorzystaniem jest problematyczne dla początkujących programistów. Jak często widzieliście w jakimś kodzie fragment typu:
cart.getProducts().add(product);
Sam pisałem niegdyś takie brzydkie rzeczy. W czasie, w którym wyrabiałem sobie moje skromne doświadczenie wypracowałem sobie jednak pewne zasady jak postępować z kolekcjami. Ktoś mógłby się ze mną spierać - nawet podając sensowne argumenty ale w mojej pracy trzymanie się tych nawyków sprawdza się i na razie się ich trzymam.

 W czym problem?

Przedstawiony wyżej fragment kodu to wyraźne złamanie zasady enkapsulacji. Skoro obiekt udostępnia swoje wnętrze (w typ przypadku jakąś kolekcję) i można z tym robić wszystko co się nam spodoba to po co te wszystkie modyfikatory dostępu i po co w ogóle taki obiekt? Skoro obiekt, który posiada jako swoją właściwość (property) jakąś listę i udostępnia ją tak po prostu na zewnątrz to może okazać się, że biedny obiekcik jest nieświadomy, że ktoś mu grzebie w brzuchu 😨.

Co począć?

Immutable collections! Coś co można by powiedzieć, że istnieje w Java od wieków bo od wersji 1.2, jednak jest to rzecz nieco ukryta przed niedoświadczonymi programistami. Kolekcja niemutowalna to taka, która co prawda posiada dane i zapewnia do nich dostęp ale nie pozwala ich dodawać, ani usuwać. Podczas próby wykonania takiej operacji jest rzucany wyjątek UnsupportedOperationException. Jak tworzyć takie kolekcje? W Java od 1.2 do 1.8 wystarczy skorzystać z klasy narzędziowej java.util.Collections i jej metod statycznych unmodifiable.... Sposób użycia:
List<String> strings;
List<String> unmodifiableStrings = Collections.unmodifiableList(strings); 

No i co ja z tego mam?

Wyobraź sobie sytuację, że piszesz sobie aplikację na zaliczenie ze swoim kolegą Ryśkiem. Napisałeś piękną klasę, przetestowaną i elegancką. Okazuje się jednak, że aplikacja działa jakoś dziwnie. Brakuje jakichś danych, a czasem są w złej kolejności. Spojrzałeś w kod Ryśka. Okazało się, że potrzebował wpisów z listy, znajdującej się w Twojej klasie i nie zrobił własnej kopii tylko operował na Twojej liście.
Korzystanie z immutable collections wymusza tworzenie kopii kolekcji! Gdybyś udostępnił własną kolekcję w postaci niemutowalnej to nikt nie miałby prawa nic namieszać.

No ale to tyle dodatkowego kodu...

No ejj... wcale nie tyle. tylko kilka znaków w linii więcej. Niestety rzeczywistość javova tak w tej chwili wygląda, że wielu udogodnień brakuje w samym języku a dostępne są zazwyczaj w postaci rozrastającego się ciągle API. W innych nowoczesnych językach takie struktury są zapewniane w inny sposób. Np. w pythonie można myśleć o krotce (tuple)  jako o liście niemodyfikowalnej. Język Kotlin z kolei zapewnia, że każda kolekcja jest domyślnie immutable o ile programista jawnie nie określi mutowalnego typu kolekcji.
Można również wykorzystać jakąś z bibliotek jak np. Guava, która dostarcza zestaw typów immutable, którymi można się posługiwać zamiast typowych kolekcji z API języka.

To kiedy stosować jaką kolekcję?

Odpowiedź jest prosta. Jeśli to tylko możliwe to zawsze stosuj kolekcje niemutowalne!
Ja stosuję 3 proste zasady:
  1. Każdy getter zawsze zwraca kolekcję immutable
  2. Przy przekazywaniu kolekcji do metody innego obiektu przekazuję kolekcję immutable
  3. Przy otrzymywaniu kolekcji jako parametr zawsze kopiuję kolekcję. Zabezpieczam się przed sytuacją gdy ktoś przekazał właśnie kolekcję immutable, a zarazem nie psuję mu życia jeśli tego nie zrobił.

Co za głupoty...

Zdaję sobie sprawę, że to co wyżej napisałem może być dla wielu czytelników oczywiste, inni mogą się nie zgodzić z niektórymi słowami. Mam jednak nadzieję, że ten tekst trafi do początkujących i skłoni ich do chwili zastanowienia. Pozdro 😊

Komentarze

  1. Oczywiste rzeczy także warto pisać, to co dla osób siedzących w temacie jest oczywiste, nie dla każdego musi takie być;) Ja wdrażam się w ten temat, dużo czytam. Także stronka https://ermlab.com i ich blog są bardzo pomocne.

    OdpowiedzUsuń

Publikowanie komentarza

Popularne posty z tego bloga

Wzorzec Open Session in View w Spring Boot

Dzisiejszy post będzie z cyklu: "Wtf? Dlaczego to działa?". A dotyczy on pewnego wzorca, którego implementacja jak się okazuje jest we frameworku Spring Boot domyślnie włączona, czego nie wszyscy programiści mogą się spodziewać. Geneza Zaczęło się od tego, że pisałem kolejny test integracyjny do kolejnego kontrolera. Test napisany, uruchamiam, zielono. No i przeglądam sobie jeszcze raz kod zanim wypchnę zmiany do review. I rzuciło mi się w oczy, że zapomniałem dodać adnotacji @Transactional (do tej pory uważałem, że to jest zawsze wymagane). I wtedy zadałem sobie przytoczone na wstępie pytanie. Dlaczego test przeszedł skoro mój serwis dociąga sobie obiekt oznaczony jako LAZY ? Dużo się nagimnastykowałem by wpisać odpowiednie query do Google'a i znaleźć odpowiedź: Open Session in View. Ale o tym za chwilę. Jak działa Hibernate Żeby lepiej zrozumieć opisywane zagadnienie warto przypomnieć jakie kroki w uproszczeniu musi wykonać Hibernate podczas komunikacji z bazą d

Spring Data - save vs saveAndFlush

Cześć, dzisiaj będzie znowu trochę o warstwie persystencji. Czasami kodzie aplikacji korzystającej ze Spring Data można napotkać użycia metody repozytorium save , a czasami saveAndFlush , a z kolei innym razem brak jakiejkolwiek z nich podczas zapisu obiektu. Wszystkie trzy metody mają swoje zastosowanie choć nieco się różnią. Teoria W teorii, różnica jest prosta. Metody save oraz saveAndFlush dodają obiekt do kontekstu persystencji danej sesji i zwracają obiekt zarządzany. Ponadto druga z nich wymusza wymusza wykonanie nagranych przez ORM akcji na bazie danych przez co dane zostają przesłane do silnika bazy. Może się to okazać przydatne w przypadku gdy w ramach jednej transakcji chcemy jeszcze wykonać kolejne zapytania, które mają być świadome wprowadzonych wcześniej zmian. Natychmiastowa synchronizacja może się również przydać gdy wykorzystujemy poziom izolacji READ_UNCOMMITTED . Czasami jednak w kodzie nie ma żadnej z nich. Wtedy możliwe jest wykonywanie tylko zapytań UPDATE

One-to-one i lazy loading

Hibernate jest frameworkiem, który nie przestaje zaskakiwać. Kryje się w nim mnóstwo tajemnic, pułapek i zagadek. Jedną taką pułapkę postaram się opisać w tym wpisie, a dotyczy ona nieoczywistego na pierwszy rzut oka zachowania adnotacji @OneToOne z parametrem fetchMode=LAZY . One-to-one Adnotacja @OneToOne służy w JPA do oznaczania pól encji, które odnoszą się do obiektów będących z nią w relacji jeden-do-jeden. W znormalizowanym schemacie relacyjnej bazy danych oznacza to sytuację, w której mamy do czynienia z dwoma tabelami A oraz B , a jedna z nich posiada kolumnę, której wartości wskazują na klucz identyfikujący krotkę w drugiej z nich. Np: Lazy fetch Adnotacja @OneToOne zawiera również atrybut fetch , który może przyjąć wartości EAGER lub LAZY (domyślnie EAGER ). O ile ustawienie wartości EAGER oznacza, że Hibernate musi pobrać dodatkowy wiersz natychmiast (za pomocą klauzuli JOIN bądź dodatkowego zapytania) to zastosowanie LAZY może ale nie musi spowodowa