Czytaj dalej...Heap, Stack i String Pool w Javie
The post Heap, Stack i String Pool w Javie appeared first on Dev Foundry.
]]>W ramach działania aplikacji Javowej, począwszy od metody main, na Stacku (Stosie) pojawiają się jedna na drugiej ramki zawierające zmienne metod. Przestrzegają przy tym zasady LIFO (Last In, First Out). Gdy dana dana metoda zakończy swoje działanie, to jest automatycznie wypychana ze Stacka.
Każdy wątek ma swój Thread Stack, na którym przechowywane są zmienne lokalne, ale jeśli są to zmienne referencyjne, to – uwaga – same obiekty do których odnoszą się te zmienne, znajdują się na Kopcu.
Załóżmy dla przykładu, że dana klasa implementująca interfejs Runnable ma jakieś pole value typu Integer. Wtedy jeśli z tej klasy zostaną utworzone np. 2 wątki, to każdy z tych wątków będzie korzystał z tego samego obiektu do którego odnosi się zmienna value. Dzieje się tak właśnie dlatego, że ten obiekt znajduje się na Heap, a nie lokalnych stackach tych wątków.
Ale jeśli dodatkowo ta sama klasa ma jakąś metodę, która będzie miała parametr int i, to kopię tego samego parametru i będą miały obydwa wątki, każdy na swoim lokalnym Stacku.
Jak już wspominałem wyżej – wszystkie obiekty w Javie znajdują się na Kopcu.
W cyklu działania danej aplikacji, obiekty mogą należeć do różnych tzw. Generations. Wyróżnia się kilka Generations obiektów i to determinuje ich miejsce w Kopcu.
Young Generation:
Eden Space – tu trafiają nowo utworzone obiekty.
Survivor Space – tu trafiają obiekty, które przetrwają cykl Garbage Collection w Eden Space.
Old Generation:
Tenured Space – tu trafiają obiekty, które przetrwały jakiś czas w Survivor Space.
Permanent Generation/Metaspace:
Miejsce w pamięci, które zawiera różne metadane a także static metody oraz zmienne.
UWAGA: Od Javy 8 to miejsce nazywa się Metaspace i różni się tym of Permanent Generation, że dynamicznie zmienia swój rozmiar w runtime aplikacji, czego Permanent Generation nie potrafiło.
Jak widać obiekty i ich miejsce w Kopcu jest też ściśle związane z Garbage Collectorem, ale o tym w innym wpisie 
Pomijając oczywiste różnice i informacje, które opisałem powyżej, Stack i Heap różnią się od siebie jeszcze pod kilkoma innymi względami:
*To samo teoretycznie może dotyczyć jakiejś zmiennej znajdującej się na końcu metody main, ale… zakładam że to dość rzadki przypadek 
String Pool przed Javą 7 był częścią Permanent Generation. Od Javy 7 String Pool jest częścią Heap space.
Stringi w Javie są immutable, to znaczy, że jeśli napiszemy:
String s1 = "Test"; String s2 = "Test";
To Java nie przydzieli dodatkowego miejsca w String Pool dla stringa, na którego wskazuje zmienna “s2”. Tylko “s1” i “s2” będą wskazywały na to samo miejsce w pamięci.
UWAGA: Wyjątkiem jest sytuacja, kiedy stworzymy nowego Stringa za pomocą słowa kluczowego “new”, np.:
String s1 = "Test";
String s2 = new String("Test");
Wtedy nawet jeśli taki literał już istnieje w String Pool, to Java utworzy nowy obiekt typu String i umieści go na Heap.
Zaletą String Poola w Heap Space jest to, że Stringi które nie są z niczym powiązane (unreferenced Strings) podlegają usunięciu przez Garbage Collector. A w przypadku Permanent Generation przed Javą 7 nie były usuwane, bo.. były częścią Permanent Generation, której nie tykał się Garbage Collector. A jako że Permanent Generation było obszarem o stałym rozmiarze, to można było dość łatwo osiągnąć Out of Memory Error przez tworzenie zbyt dużej ilości nowych obiektów typu Stringów.
The post Heap, Stack i String Pool w Javie appeared first on Dev Foundry.
]]>Czytaj dalej...Słowo kluczowe Static w Javie
The post Słowo kluczowe Static w Javie appeared first on Dev Foundry.
]]>Dlatego też pierwszy kod, który wykonuje się w Javie pochodzi z bloków static. Najpierw klasa wczytywana jest do Class Loadera, a dopiero później jakiekolwiek obiekty tej klasy mogą zostać utworzone. I dopiero wtedy wykonywany jest kod, który znajduje się w konstruktorze danej klasy.
Słowo kluczowe static sprawia, że dane pole będzie przypisane do klasy, a nie do danej instancji tej klasy, a więc do obiektu tej klasy.
Może to być przydatne na przykład w sytuacji, gdy wiemy i chcemy zrobić tak, żeby wszystkie obiekty danej klasy miały jakąś część wspólną. Taką częścią może być właśnie jakaś wartość pola statycznego.
Zmienną statyczną wywołujemy pisząc przed nią nazwę klasy:
Bicycle.numberOfBicycles
Aczkolwiek można się do niej odnieść również poprzez zmienną referencyjną, chociaż jest to odradzane, bo wtedy nie widać na pierwszy rzut oka, że wywoływana metoda jest typu static:
myBike.numberOfBicycles
Wartość zmiennej statycznej można ustawić ręcznie przy deklaracji, w bloku static, ale również w konstruktorze. Wtedy przy każdorazowym tworzeniu instancji danej klasy, będzie ustawiana/inkrementowana wartość takiej zmiennej. Można w ten sposób na przykład zaimplementować licznik instancji danej klasy (w konstruktorze inkrementuje się wartość zmiennej statycznej).
Bardzo podobnie do zmiennych, statyczne metody należą do klasy, a nie do jej instancji.
Można się do nich odnosić przez nazwę klasy lub zmienną referencyjną (co ponownie – nie jest zalecane).
Można z nich normalnie korzystać z jedną uwagą.
UWAGA: W obrębie danej klasy, ze środka metod static NIE MOŻNA bezpośrednio wywoływać zwykłych zmiennych lub metod, bo należą one do danej instancji klasy, a nie do klasy jako takiej (więc kod w metodzie statycznej nie wiedziałby do jakiej instancji ma się zastosować). Dotyczy to również słowa kluczowego “this”, bo metoda statyczna nie zna żadnej instancji, do której może odnosić się “this”. Oczywiście ze środka metod static nadal można odnosić się do zwykłych zmiennych i metod, jeśli odbywa się to za pomocą zmiennej referencyjnej danego obiektu.
Poza tym dowolna kombinacja zmiennych i metod oraz ich wywołań jest dozwolona.
Słowo kluczowe static w kombinacji z final, daje nam coś na kształt stałej, ponieważ jest jedna jej instancja oraz po przypisaniu jej jakiejś wartości, nie można jej zmienić. Zatem jeśli od razu przy deklaracji mamy też inicjalizację takiej zmiennej, to faktycznie jest ona stałą:
private static final double PI = 3.141592653589793;
Kod z bloków static jest wykonywany w momencie wczytywania tej klasy przez Class Loadera (co dzieje się gdy JVM po raz pierwszy natknie się na tę klasę w kodzie), podobnie jak w przypadku metod oraz zmiennych typu static. Taką inicjalizację można też wymusić gdzieś w kodzie, który wiemy, że będzie się wykonywał jako pierwszy lub jeden z pierwszych, pisząc:
StaticClass.init()
lub:
Class.forName(StaticClass)
Dana klasa może mieć więcej niż jeden blok static.
Alternatywą dla bloku static jest wykorzystanie prywatnej, statycznej metody przy inicjalizacji pola static:
class ExampleClass {
public static String stringVariable = initializeClassVariable();
private static String initializeClassVariable() {
//kod inicjalizacyjny
}
}
The post Słowo kluczowe Static w Javie appeared first on Dev Foundry.
]]>Czytaj dalej...Modyfikatory dostępu w języku Java
The post Modyfikatory dostępu w języku Java appeared first on Dev Foundry.
]]>W języku Java istnieją cztery modyfikatory dostępu (zwane również modyfikatorami widoczności). Każdy z nich określa czy dana klasa, metodą bądź pole klasy jest widoczne dla innych klas. W języku Java istnieją cztery poziomy, zaczynając od najszerszego są to – public, protected, default (package), private. Trzeba pamiętać, że choć modyfikatory dostępu są cztery to słów kluczowych je określających jest już tylko trzy – private, public i protected. Poziom package jest poziomem domyślnym i jeśli chcemy go użyć to po prostu nie dodajemy żadnego modyfikatora dostępu przed nazwą pola czy metody.
W filmie poniżej znajdziesz opis na praktycznym przykładzie, zapraszam do obejrzenia.
Public jest najszerszym poziomem widoczności – klasy, pola i metody oznaczone w ten sposób są widoczne dla wszystkich innych klas.
Drugi w kolejności – protected– jest dostępny dla klas zdefiniowanej w tej samej paczce oraz w klasach dziedziczących (extends) po klasie, która zawiera pola czy metody oznaczone jako protected.
Package, będący domyślnym poziomem widoczności nieposiadającym własnego modyfikatora, ogranicza widoczność do klas z tej samej paczki.
Finalnie mamy private– czyli prywatne. Jak sama nazwa wskazuje elementy z dostępem na poziomie prywatnym są widoczne tylko dla struktur zdefiniowanych w tej samej klasie.
Jeśli dowiedziałeś się dzięki temu wpisowi czegoś nowego, będzie nam niezmiernie miło, gdybyś podzielił się tym postem w swoich social mediach.
The post Modyfikatory dostępu w języku Java appeared first on Dev Foundry.
]]>Czytaj dalej...Różnice pomiędzy final, finally, a finalize
The post Różnice pomiędzy final, finally, a finalize appeared first on Dev Foundry.
]]>final i finally oraz metody finalize. Funkcjonalności te, wbrew pozorom, poza zbliżonymi nazwami nie mają ze sobą nic wspólnego.
Na poniższym filmie omawiam działanie każdego z nich:
Final jest słowem kluczowym, które użyte ze zmienną zamienia ją w stałą. Natomiast użyte wraz z metodą powoduje, iż nie można jej nadpisać. Można również wykorzystać je wraz z klasą – wówczas blokuje ono możliwość dziedziczenia danej klasy.
Finally jest elementem bloku try-catch-finally. Operacje zawarte w tej sekcji zostaną zawsze wykonane – niezależnie czy kod zawarty w sekcji catch zakończył się sukcesem, czy rzucił wyjątkiem.
Finallize jest natomiast metodą z klasy Object, która w teorii miała być wywoływana przed tym, gdy dany obiekt zostanie usunięty z pamięci. W praktyce nie zawsze (albo wręcz z reguły) nie działała w 100%. Obecnie oznaczona jest jako deprecated i nie powinna być używana pod żadnym pozorem.
Jeśli ten post Ci się przydał podziel się nim proszę w swoich social mediach 
The post Różnice pomiędzy final, finally, a finalize appeared first on Dev Foundry.
]]>Czytaj dalej...Testy jednostkowe – Mocki
The post Testy jednostkowe – Mocki appeared first on Dev Foundry.
]]>Ten wpis jest drugą częścią miniserii o stubach oraz mockach. Poznamy w nim zalety mocków, a także ich ogólną charakterystykę i zastosowanie. Pod tym adresem znajdziesz część pierwszą, w której omawiane są stuby.
Aby w ogóle móc skorzystać z obiektów mockowych, należy dodać do projektu zależność w postaci frameworka Mockito. Najlepiej ściągnąć najnowszą wersję, aktualnie jest to wersja 2.25.
My polecamy skorzystanie z zależności, która jest przystosowana do współpracy z frameworkiem JUnit – mockito-junit-jupiter.
Odpowiedni artefakt znajdziemy korzystając na przykład z wyszukiwarki na stronie maven.org lub też bezpośrednio z tego odnośnika. Pod tym adresem znajdziemy gotowy plik *.jar lub też gotowy wpis, który możemy wkleić do pliku konfiguracyjnego Mavena lub Gradla.
Mocki to obiekty, których zadaniem jest symulacja działania normalnych obiektów i kodu. Natomiast zadaniem programisty jest odpowiednie zaprogramowanie działania mocka.
Na przykład: jeżeli metoda zostanie wywołana obiekcie mockowym X, to ma zwrócić wartość Y.
Mocki mogą być tworzone dynamicznie w czasie runtime’u aplikacji i są znacznie bardziej elastyczne w porównaniu do stubów. Zapewniają też znacznie więcej funkcjonalności.
Aby porównać mocki bezpośrednio ze stubami, utworzymy klasę testową AccountServiceTest. Jej zadanie będzie takie samo, jak klasy testowej AccountServiceStubTest, gdzie testowaliśmy metodę z serwisu AccountService. (we wpisie z zeszłego tygodnia dotyczącym stubów)
Możemy więc skopiować cały kod z klasy AccountServiceStubTest. Nasza klasa testowa będzie zatem wyglądała tak:
@Test
void getAllActiveAccounts() {
//given
AccountRepository accountRepositoryStub = new AccountRepositoryStub();
AccountService accountService = new AccountService(accountRepositoryStub);
//when
List accountList = accountService.getAllActiveAccounts();
//then
assertThat(accountList, hasSize(2));
}
Aby teraz zamienic stuba na obiekt mockowy, możemy skorzystać ze specjalnej metody frameworka Mockito – mock. Ta metoda pozwala nam na utworzenie obiektu mockowego z danej klasy. W naszym przykładzie będzie to oczywiście klasa AccountRepository:
AccountRepository accountRepositoryMock = mock(AccountRepository.class);
I takiego mocka możemy teraz przekazać do konstruktora klasy AccountService:
AccountRepository accountRepositoryMock = mock(AccountRepository.class); AccountService accountService = new AccountService(accountRepositoryMock);
Zatem jak było widać na powyższym przykładzie, w bardzo prosty sposób jesteśmy w stanie zamienić obiekt stubowy na obiekt mockowy.
Jednak jeśli teraz uruchomimy tę metodę testową, to asercja nie zostanie spełniona, a test nie przejdzie. W wyniku zobaczymy, że była oczekiwana wartość 2, a w rzeczywistości zwrócona została wartość 0, a więc pusta lista. Dlaczego?
Dzieje się tak dlatego, że w Mockito wykorzystywane są tak zwane „nice mocki„. Działają one w specyficzny sposób. Załóżmy, że mamy taką sytuację: w klasie, którą mockujemy znajdują się metody, które zwracają jakieś wartości. Wtedy domyślnie Mockito przy wywołaniu takich metod postara się zwrócić jakieś sensowne wartości, a nie na przykład po prostu wartość null.
W naszym przykładzie, gdzie zwracana jest lista obiektów typu Account, Mockito zwraca pustą listę. A jeśli metoda miałaby zwracać wartości Integer, to Mockito zwróciłoby wartość 0. Jeśli metoda zwracałaby wartość boolean, to Mockito zwróciłoby wartość false. Więcej informacji można znaleźć w dokumentacji Mockito.
W taki sposób będą zachowywały się mocki, jeśli nie zaprogramujemy dla nich żadnego działania. Ale właśnie… przecież możemy to zachowanie zaprogramować!
Najpierw musimy przygotować listę danych testowych. Na szczęście mamy już taki zestaw w naszej klasie stubowej, znanej z poprzedniego wpisu. A żeby nie zaśmiecać naszej metody testowej, to dodatkowo ten kod umieścimy w metodzie pomocniczej:
private ListprepareAccountData() { Address address1 = new Address("Kwiatowa", "33/5"); Account account1 = new Account(address1); Account account2 = new Account(); Address address2 = new Address("Piekarska", "12b"); Account account3 = new Account(address2); return Arrays.asList(account1, account2, account3); }
Dla przypomnienia, klasa Account ma postać:
class Account {
private boolean active;
private Address defaultDeliveryAddress;
Account() {
this.active = false;
}
Account(Address defaultDeliveryAddress) {
this.defaultDeliveryAddress = defaultDeliveryAddress;
if(defaultDeliveryAddress != null) {
activate();
} else {
this.active = false;
}
}
void activate() {
this.active = true;
}
boolean isActive() {
return this.active;
}
Address getDefaultDeliveryAddress() {
return defaultDeliveryAddress;
}
void setDefaultDeliveryAddress(Address defaultDeliveryAddress) {
this.defaultDeliveryAddress = defaultDeliveryAddress;
}
}
Jeśli przy tworzeniu obiektu typu Account skorzystamy z konstruktora bezargumentowego lub też w konstruktorze oczekującym obiektu typu Address przekażemy wartość null, to wtedy utworzone konto będzie nieaktywne, a więc metoda isActive będzie zwracała wartość false. W przeciwnym razie oczywiście zwrócona będzie wartość true.
W naszym przykładzie utworzone zostały 3 konta: account1, account2 i account3. account2 zostało utworzone korzystając z konstruktora bezargumentowego, więc to konto będzie nieaktywne. account1 i accoun3 natomiast będą aktywne.
Możemy teraz wrócić do naszej metody testowej: getAllActiveAccounts . Tutaj chcielibyśmy móc zaprogramować takie działanie:
Jeżeli na mocku zostanie wywołana metoda getAllAccounts , to w wyniku powinna być zwrócona lista kont z metody prepareAccountData.
Aby to zrobić, korzystamy ze specjalnych metod pomocniczych z biblioteki Mockito: when oraz thenReturn:
when(accountRepository.getAllAccounts()).thenReturn(accounts);
Taka składnia jest popularna, ale może być dla kogoś myląca, bo mamy tu kombinację słów when i then, dość podobne do samych sekcji //given, //when i //then.
Zatem żeby zachować ducha Behaviour Driven Development (BDD), można skorzystać z innej kombinacji metod: given i willReturn.
Po zmianach cała metoda testowa prezentuje się następująco:
@Test
void getAllActiveAccounts() {
//given
List accounts = prepareAccountData();
AccountRepository accountRepository = mock(AccountRepository.class);
AccountService accountService = new AccountService(accountRepository);
given(accountRepository.getAllAccounts()).willReturn(accounts);
//when
List accountList = accountService.getAllActiveAccounts();
//then
assertThat(accountList, hasSize(2));
}
I teraz gdy uruchomimy testy, czeka nas miły widok:

Powyższy przykład już jasno pokazuje, że mocki są znacznym ułatwieniem względem stubów. Chociażby dlatego, że nie musimy tworzyć osobnej klasy stubowej. Ale jeszcze większą zaletą jest to, jak łatwo możemy tworzyć kolejne scenariusze testowe.
Załóżmy, że chcielibyśmy przetestować sytuację, w której z bazy danych nie zwrócone zostaną żadne konta.
Zatem do klasy testowej dopisujemy kolejną metodę. Nazwiemy ją getNoActiveAccounts.
Część główną metody możemy dla ułatwienia skopiować z metody getAllActiveAccounts. Dodatkowo musimy tutaj dokonać zmiany logicznej. Oczekujemy, że lista kont, którą otrzymamy z bazy danych, będzie pusta. Zmieniamy zatem asercję z:
assertThat(accountList, hasSize(2));
na:
assertThat(accountList, hasSize(0));
I modyfikujemy zawartość metody thenReturn tak, aby obiekt mockowy faktycznie zwracał nam pustą listę (można tu skorzystać na przykład z utilsowej metody Javowej):
given(accountRepository.getAllAccounts()).willReturn(Collections.emptyList());
Ostatecznie cała metoda testowa wygląda tak:
@Test
void getNoActiveAccounts() {
//given
AccountRepository accountRepository = mock(AccountRepository.class);
AccountService accountService = new AccountService(accountRepository);
given(accountRepository.getAllAccounts()).willReturn(Collections.emptyList());
//when
List accountList = accountService.getAllActiveAccounts();
//then
assertThat(accountList, hasSize(0));
}
Możemy teraz ją uruchomić. Asercja powinna być spełniona i ponownie powinniśmy ujrzeć zielony kolor. To takie proste!
A jeśli w interfejsie pojawią się nowe metody, to również w niczym to nie przeszkodzi. Wtedy po prostu utworzy się stosownego mocka i zaprogramuje odpowiednie działanie.
Powyższe przykłady jasno pokazują dlaczego mocki są takie popularne i dlaczego zazwyczaj są lepszym rozwiązaniem od stubów. Dają programistom bardzo duże możliwości i zapewniają elastyczność w działaniu. Dzięki nim można też przetestować znacznie więcej scenariuszy testowych, oszczędzając przy tym dużo czasu.
The post Testy jednostkowe – Mocki appeared first on Dev Foundry.
]]>Czytaj dalej...Testy jednostkowe – Stuby
The post Testy jednostkowe – Stuby appeared first on Dev Foundry.
]]>Ten wpis jest pierwszą częścią miniserii o stubach oraz mockach. Poznamy w nim wady oraz zalety stubów, a także ich ogólną charakterystykę i zastosowanie. W kolejnej części – bliżej przyglądamy się mockom.
Naszą bazą kodową, którą chcielibyśmy przetestować, jest aplikacja do zamawiania jedzenia online. Składa się z prostych klas takich jak Account, Order, Cart czy też Meal. W tym wpisie operować będziemy głównie na klasie Account:
class Account {
private boolean active;
private Address defaultDeliveryAddress;
Account() {
this.active = false;
}
Account(Address defaultDeliveryAddress) {
this.defaultDeliveryAddress = defaultDeliveryAddress;
if(defaultDeliveryAddress != null) {
activate();
} else {
this.active = false;
}
}
void activate() {
this.active = true;
}
boolean isActive() {
return this.active;
}
Address getDefaultDeliveryAddress() {
return defaultDeliveryAddress;
}
void setDefaultDeliveryAddress(Address defaultDeliveryAddress) {
this.defaultDeliveryAddress = defaultDeliveryAddress;
}
}
Jest to prosta klasa typu POJO. Jedyną porcją logiki są konstruktory. Jeśli przy tworzeniu obiektu typu Account skorzystamy z konstruktora bezargumentowego lub też w konstruktorze oczekującym obiektu typu Address przekażemy wartość null, to wtedy utworzone konto będzie nieaktywne, a więc metoda isActive będzie zwracała wartość false. W przeciwnym razie oczywiście zwrócona będzie wartość true.
Teraz przyszedł czas, aby nieco rozbudować naszą aplikację. Chcemy dodać do niej nową funkcjonalność – możliwość pobrania listy klientów z bazy danych, tak aby wysłać im na przykład jakieś wiadomości promocyjne.
Aby to zrobić najpierw tworzymy nowy interfejs, który nazwiemy AccountRepository:
public interface AccountRepository {
List getAllAccounts();
}
W interfejsie umieściliśmy deklarację jednej metody – getAllAccounts, która ma oczywiście służyć do pobrania kont wszystkich klientów.
Kolejnym naturalnym krokiem byłoby utworzenie implementacji powyższego interfejsu. Jednak aby to zrobić rzetelnie, należałoby najpierw przygotować całą konfigurację połączenia z bazą danych, co z kolei zajęłoby sporo czasu, a przecież nie to jest głównym tematem tego wpisu.
Można też na to spojrzeć w taki sposób, że w naszej aplikacji mamy dostęp tylko i wyłącznie do powyższej metody z interfejsu, a nie do samej jej implementacji. Może być przecież tak, że implementacją zajmuje się inny zespół albo też dane które nas interesują, możemy otrzymywać z jakiegoś innego, zewnętrznego systemu lub API. Załóżmy więc, że nie mamy bezpośredniego dostępu do metody implementującej. Zobaczmy więc, czy mimo takiej niedogodności będziemy w stanie odpowiednio przetestować interesujący nas kod.
Do wyciągania informacji o kontach potrzebny nam będzie serwis. Utworzymy więc stosowną klasę serwisową: AccountService, w którym będziemy mieli jedną zależność. Tą zależnością będzie oczywiście AccountRepository, więc dodajemy ją w konstruktorze:
class AccountService {
private AccountRepository accountRepository;
AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
}
W powyższej klasie chcielibyśmy umieścić metodę, która będzie zwracała tylko konta aktywne. Nazwiemy ją getActiveAccounts:
ListgetAllActiveAccounts() { return accountRepository.getAllAccounts().stream() .filter(Account::isActive) .collect(Collectors.toList()); }
Jak widać metoda ta opiera się na danych, które ma zwracać metoda getAllAccounts, będąca implementacją metody z interfejsu AccountRepository. Metoda pobiera konta użytkowników, następnie filtruje je korzystając z metody isActive z klasy Account.
Nasz kod jest już gotowy do przetestowania. Tworzymy więc klasę testową AccountServiceStubTest, a w niej metodę getActiveAccounts:
@Test
void getAllActiveAccounts() {
//given
AccountService accountService = new AccountService();
W sekcji given chcieliśmy utworzyć obiekt klasy AccountService, ale pojawił się problem, ponieważ w konstruktorze musimy podać klasę, która implementuje interfejs AccountRepository, a my nie mamy instancji takiej klasy.
I właśnie w takiej sytuacji mogą nam pomóc mocki lub stuby. Zajmijmy się zatem stubami.
Stuby to przykładowe implementacje jakiegoś kodu, którego zachowanie chcemy przetestować. Jeśli – tak jak w naszym przykładzie – nie mamy dostępu do prawdziwej metody, która będzie nam zwracała dane, to sami powinniśmy sobie taką metodę utworzyć. Musimy napisać ją w taki sposób, aby zwracała nam zestaw przykładowych danych.
Utwórzmy sobie zatem takiego stuba. Zaczniemy od stworzenia nowej klasy, a jako że stuby są nierozłącznie związane z testami jednostkowymi, to klasy stubowe najlepiej tworzyć w tej samej paczce, w której są normalne klasy testowe. Naszą klasę stubową nazwiemy dość nieoryginalnie – AccountRepositoryStub. Klasa ta powinna implementować interfejs AccountRepository i nadpisywać metodę getAllAccounts:
public class AccountRepositoryStub implements AccountRepository {
@Override
public List getAllAccounts() {
Address address1 = new Address("Kwiatowa", "33/5");
Account account1 = new Account(address1);
Account account2 = new Account();
Address address2 = new Address("Piekarska", "12b");
Account account3 = new Account(address2);
return Arrays.asList(account1, account2, account3);
}
}
Implementacja tej metody jest bardzo prosta: tworzymy nowe konta użytkowników i zwracamy je w postaci listy – zgodnie z deklaracją metody getAllAccounts.
Teraz możemy wrócić do naszego testu i w konstruktorze serwisu przekazać naszego stuba:
@Test
void getAllActiveAccounts() {
//given
AccountRepository accountRepositoryStub = new AccountRepositoryStub();
AccountService accountService = new AccountService(accountRepositoryStub);
A skoro wiemy jakie dane zwraca nasze stub, to wiemy też jaką asercję należy napisać, żeby test przeszedł pozytywnie. Zatem cały test prezentuje się tak:
@Test
void getAllActiveAccounts() {
//given
AccountRepository accountRepositoryStub = new AccountRepositoryStub();
AccountService accountService = new AccountService(accountRepositoryStub);
//when
List accountList = accountService.getAllActiveAccounts();
//then
assertThat(accountList, hasSize(2));
}
Zakładaliśmy, że zwrócona lista powinna mieć 2 elementy, ponieważ dwa utworzone przez nas w metodzie stubowej konta miały w konstruktorze przekazany adres, a więc tylko te dwa konta są aktywne.
W ten właśnie sposób działają stuby.
Jednak tutaj mamy tylko jeden scenariusz testowy, bo stub zwraca nam jeden, konkretny zestaw danych. A co jeśli teraz chcielibyśmy przeprowadzić inny test? Co jeśli na przykład chcielibyśmy sprawdzić co będzie, jeśli z repozytorium nie zwrócone zostaną żadne konta aktywne?
W tej chwili nasz stub zwraca 2 konta aktywne i nie można w nim dodać kolejnej metody, ponieważ tylko i wyłącznie ta jedna, jedyna metoda może być implementacją metody z interfejsu. Zatem żeby przetestować inny scenariusz, trzeba by dodać kolejną klasę stubową implementującą interfejs AccountRepository. Następnie w tejże klasie utworzyć utworzyć kolejną metodę która zwracałaby inny zestaw danych.
I jak łatwo się domyślić wkrótce tych klas i metod stubowych mogłoby się zrobić bardzo dużo. A im bardziej skomplikowana metoda, tym więcej klas, i metod.
Jest jeszcze jedna kwestia. A co jeśli do interfejsu dodana zostanie kolejna metoda? Wtedy trzeba by ją implementować we wszystkich klasach stubowych, żeby móc je odpowiednio przetestować i uniknąć błędu kompilacji. Bardzo szybko może się zatem okazać, że klasy stubowe są bardzo ciężkie w utrzymaniu.
Dla bardzo prostych klas, metod i przykładów, w których jesteśmy pewni, że nie będą się rozrastać, stuby spełniają swoje zadanie. Jednak przy większej liczbie warunków testowych oraz przy możliwym rozroście interfejsów, są one niestety kiepskim rozwiązaniem. W tych sytuacjach znacznie lepiej skorzystać z mocków, którymi zajmiemy się w kolejnej części tej miniserii.
The post Testy jednostkowe – Stuby appeared first on Dev Foundry.
]]>Czytaj dalej...Testy jednostkowe – JUnit 5 i Mockito 2 – nowy kurs!
The post Testy jednostkowe – JUnit 5 i Mockito 2 – nowy kurs! appeared first on Dev Foundry.
]]>Testy jednostkowe – JUnit 5 i Mockito 2
Testy jednostkowe są jedną z najważniejszych technik, które powinien znać każdy programista, niezależnie od języka w którym tworzy.
Nasz kurs jest dedykowany wszystkim, którzy chcą zdobyć lub poszerzyć swoją wiedzę na temat testów jednostkowych, frameworków JUnit 5 oraz Mockito 2, testowania w metodyce Test Driven Development oraz najlepszych praktyk i konwencji w tworzeniu testów jednostkowych.
W kursie dowiesz się między innymi:
… i dużo więcej! 
Poniżej znajdziecie link z kodem zniżkowym do kursu:
https://www.udemy.com/testy-jednostkowe
Serdecznie zapraszamy! 
The post Testy jednostkowe – JUnit 5 i Mockito 2 – nowy kurs! appeared first on Dev Foundry.
]]>Czytaj dalej...JUnit 5 – Extension Model
The post JUnit 5 – Extension Model appeared first on Dev Foundry.
]]>W JUnicie 4 mieliśmy do dyspozycji test runnery oraz Rules.
Test runnery odpowiadały za uruchamianie testów i jeśli nie określiliśmy tego inaczej, to domyślnie wykorzystywany był test runner JUnitowy. Jeśli jednak chcieliśmy skorzystać z jakiegoś innego – gotowego lub customowego – test runnera, na przykład bardzo popularny był (i nadal zresztą jest) test runner do frameworka Mockito, to musieliśmy użyć adnotacji @RunWith:
@RunWith(MockitoJUnitRunner.class)
Natomiast Rules były wykorzystywane do tego, aby w klasach i metodach testowych korzystać z jakichś gotowych funkcjonalności.
Załóżmy, że we wszystkich metodach testowych, w wielu klasach testowych chcielibyśmy przed uruchomieniem testów przygotować specjalny plik tekstowy. W obrębie jednej klasy testowej, żeby nie powtarzać takiego kodu w każdej metodzie testowej, można by taki kod umieścić w metodzie z adnotacją @Before (w JUnicie 5 jest to adnotacja @BeforeEach). Dzięki temu odpowiednie działanie byłoby wykonane przed uruchomieniem każdej metody testowej.
Natomiast w tym przykładzie chcemy z takiego kodu korzystać w wielu klasach. Bez sensu byłoby zatem ten sam kod umieszczać w każdej klasie, w metodzie z adnotacją @Before. Wówczas kod nie powtarzałby się co prawda w obrębie klasy, ale w obrębie na przykład paczki – już tak. Żeby temu zaradzić możemy skorzystać z funkcjonalności, którą zapewnia nam zestaw Rules.
Żeby z nich skorzystać, należy najpierw utworzyć instancję interesującej nas klasy (już istniejącej, gotowej z paczki org.junit.rules) i umieścić nad nią adnotację @Rule:
@Rule private TemporaryFolder temporaryFolder = new TemporaryFolder();
Tutaj mamy przykład klasy TemporaryFolder, dzięki której można utworzyć jakiś plik albo katalog, który jest automatycznie usuwany po zakończeniu danego testu jednostkowego:

I tutaj, jak widać, korzystamy z gotowych, zdefiniowanych metod i możemy to robić w dowolnej ilości klas testowych bez ryzyka powtarzania kodu.
W JUnicie 5 mamy za to do dyspozycji Extension Model. Działa on na podobnej zasadzie do Rules, ale zamiast korzystać z gotowej funkcjonalności, mamy do dyspozycji zestaw interfejsów, których metody implementujemy, aby zaczepić się w odpowiednim miejscu life cycle (cyklu życia) danego testu, żeby wykonać tam jakieś działanie.
Cykl życia testu oznacza po prostu kolejność operacji, które są kolejno uruchamiane przez mechanizm przetwarzający daną metodę testową (w przypadku JUNita 5 jest to silnik JUnit Jupiter) w ramach danego testu.
I ta kolejność na przykładzie interfejsów z paczki org.junit.jupiter.api.extension, których metody możemy implementować, prezentuje się mniej więcej tak:
Lub jeśli ktoś woli uproszczoną wersję graficzną:

Pomarańczowym kolorem zaznaczone są znane nam adnotacje umieszczane nad metodami, które powinny być uruchamiane przed wszystkimi testami z danej klasy (adnotacja @BeforeAll), przed każdym pojedynczym testem w klasie (@BeforeEach), po każdym pojedynczym teście (@AfterEach) i po wszystkich testach w klasie (@AfterAll).
Natomiast niebieskim kolorem zaznaczone są Callbacks, będące interfejsami z cyklu życia danego testu. I to właśnie ich metody możemy implementować – wówczas będą one uruchamiane w takiej kolejności, jak na grafice powyżej.
Zatem, jeśli na przykład zaimplementujemy metodę interfejsu BeforeEachCallback, to kod takiej metody będzie wykonywany po metodzie oznaczonej adnotacją @BeforeAll, a przed metodą oznaczoną adnotacją @BeforeEach.
Zobaczmy teraz w jaki sposób możemy to wykorzystać w jakimś przykładzie. Mamy już przygotowaną klasę testową OrderTest:
class OrderTest {
private Order order;
@BeforeEach
void initializeOrder() {
System.out.println("Before each");
order = new Order();
}
@AfterEach
void cleanUp() {
System.out.println("After each");
order.cancel();
}
@Test
void testAssertArrayEquals() {
//given
int[] ints1 = {1, 2, 3};
int[] ints2 = {1, 2, 3};
//then
assertArrayEquals(ints1, ints2);
}
@Test
void mealListShouldBeEmptyAfterCreationOfOrder() {
//then
assertThat(order.getMeals(), empty());
assertThat(order.getMeals().size(), equalTo(0));
assertThat(order.getMeals(), hasSize(0));
assertThat(order.getMeals(), emptyCollectionOf(Meal.class));
}
@Test
void addingMealToOrderShouldIncreaseOrderSize() {
//given
Meal meal = new Meal(15, "Burger");
Meal meal2 = new Meal(5, "Sandwich");
//when
order.addMealToOrder(meal);
//then
assertThat(order.getMeals(), hasSize(1));
assertThat(order.getMeals(), contains(meal));
assertThat(order.getMeals(), hasItem(meal));
assertThat(order.getMeals().get(0).getPrice(), equalTo(15));
}
@Test
void removingMealFromOrderShouldDecreaseOrderSize() {
//given
Meal meal = new Meal(15, "Burger");
//when
order.addMealToOrder(meal);
order.removeMealFromOrder(meal);
//then
assertThat(order.getMeals(), hasSize(0));
assertThat(order.getMeals(), not(contains(meal)));
}
@Test
void mealsShouldBeInCorrectOrderAfterAddingThemToOrder() {
//given
Meal meal1 = new Meal(15, "Burger");
Meal meal2 = new Meal(5, "Sandwich");
//when
order.addMealToOrder(meal1);
order.addMealToOrder(meal2);
//then
assertThat(order.getMeals(), containsInAnyOrder(meal2, meal1));
}
@Test
void testIfTwoMealListsAreTheSame() {
//given
Meal meal1 = new Meal(15, "Burger");
Meal meal2 = new Meal(5, "Sandwich");
Meal meal3 = new Meal(11, "Kebab");
List meals1 = Arrays.asList(meal1, meal2);
List meals2 = Arrays.asList(meal1, meal2);
//then
assertThat(meals1, is(meals2));
}
}
Znajduje się w niej sześć metod testowych i dwie metody z adnotacjami @BeforeEach oraz @AfterEach.
Są to dość małe testy z prostymi asercjami, których zadaniem jest sprawdzenie działania klasy Order:
class Order {
private List meals = new ArrayList<>();
void addMealToOrder(Meal meal) {
this.meals.add(meal);
}
void removeMealFromOrder(Meal meal) {
this.meals.remove(meal);
}
List getMeals() {
return meals;
}
void cancel() {
this.meals.clear();
}
}
Powiązaną klasą jest również klasa Meal:
class Meal {
private int price;
private String name;
Meal(int price) {
this.price = price;
}
Meal(int price, String name) {
this.price = price;
this.name = name;
}
}
I załóżmy, że chcielibyśmy napisać kod, który byłby wykonywany przed każdym wywołaniem metody z adnotacją @BeforeEach oraz po każdym wywołaniu metody z adnotacją @AfterEach. Dotyczyłoby to zatem każdego testu jednostkowego w klasie OrderTest.
Żeby to zrobić, musimy najpierw utworzyć nową klasę – BeforeAfterExtension. Ta klasa powinna implementować interfejsy BeforeEachCallback oraz AfterEachCallback (można się posiłkować grafiką powyżej):
public class BeforeAfterExtension implements BeforeEachCallback, AfterEachCallback {
}
Kolejnym krokiem jest zaimplementowanie metod zadeklarowanych w tych interfejsach. Będzie to metoda beforeEach dla interfejsu BeforeEachCallback oraz metoda afterEach dla interfejsu AfterEachCallback.
I żeby niepotrzebnie nie komplikować tego przykładu, w środku tych metod wyświetlimy prosty komunikat:
public class BeforeAfterExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext extensionContext) {
System.out.println("Inside before each extension");
}
@Override
public void afterEach(ExtensionContext extensionContext) {
System.out.println("Inside after each extension");
}
}
I to tyle! Teraz możemy powrócić do klasy testowej OrderTest.
Aby skorzystać z rozszerzenia, które utworzyliśmy wcześniej, musimy umieścić nad klasą testową specjalną adnotację @ExtendWith. Jako argument tej adnotacji podajemy nazwę naszej klasy rozszerzenia i dodajemy końcówkę .class:
@ExtendWith(BeforeAfterExtension.class)
class OrderTest {
Wówczas takie rozszerzenie będzie stosowane dla całej klasy, a więc dla wszystkich metod testowych. Możemy jednak ograniczyć je tylko do jednej metody. Wtedy adnotacje umieścić należy nad wybraną metodą.
Gdy teraz uruchomimy wszystkie testy z klasy OrderTest, to w konsoli otrzymamy komunikat:
Inside before each extension Inside after each extension Inside before each extension Inside after each extension Inside before each extension Inside after each extension Inside before each extension Inside after each extension Inside before each extension Inside after each extension Inside before each extension Inside after each extension
Widać zatem, że para wiadomości:
Inside before each extension Inside after each extension
została powtórzona sześć razy – tyle ile jest metod testowych w tej klasie. A więc wszystko się zgadza.
Jednak żeby upewnić się, że na pewno metody z naszego rozszerzenia uruchamiają się przed i po metodach z adnotacjami @BeforeEach i @AfterEach, to do nich również dodamy proste komunikaty:
@BeforeEach
void initializeOrder() {
System.out.println("Before each");
order = new Order();
}
@AfterEach
void cleanUp() {
System.out.println("After each");
order.cancel();
}
Teraz uruchomimy już tylko jeden test – testAssertArrayEquals, żeby nie widzieć wszystkich komunikatów powtórzonych sześciokrotnie:
Inside before each extension Before each After each Inside after each extension
I widzimy, że zgodnie z planem, metody z naszej klasy rozszerzeń – BeforeAfterExtension – były uruchomione odpowiednio przed oraz po metodach z adnotacjami @BeforeEach oraz @AfterEach.
Dla osób, które tęsknią za dawną funkcjonalnością Rules, a jednocześnie chcą się przenieść na JUnita 5 – proszę nie panikować! Społeczność programistyczna przygotowała listę silników testowych oraz rozszerzeń, z których można korzystać we własnych projektach: https://github.com/junit-team/junit5/wiki/Third-party-Extensions
Na powyższej liście znajdziemy między innymi pozycję zatytułowaną „JUnit Extensions”: https://glytching.github.io/junit-extensions/index.
Znajduje się tam kilka rozszerzeń, które są odpowiednikami popularnych Rules z JUNita 4. Wśród nich jest także znany nam z początku wpisu TemporaryFolder: https://glytching.github.io/junit-extensions/temporaryFolder
Chociaż przykład podany w tym wpisie był dość prosty, to myślę, że dość dobrze pokazuje jak duże możliwości daje Extension Model. I tak naprawdę tylko od nas zależy w jaki sposób zdecydujemy się wykorzystać jego potencjał.
Kod z przykładami do powyższego wpisu znajdziesz na githubie.
The post JUnit 5 – Extension Model appeared first on Dev Foundry.
]]>Czytaj dalej...Zapis i odczyt plików w Java 7+
The post Zapis i odczyt plików w Java 7+ appeared first on Dev Foundry.
]]>Zacznijmy od utworzenia naszego przykładowego pliku z danymi:
EmployeeData Paweł,Ćwik 32 y EmployeeData Dawid,Nowak 32 n
Plik przechowuje informacje o pracowniku – jego imię, nazwisko, wiek oraz czy jest zatrudniony na pełny etat. Stwórzmy więc w pierwszej kolejności klasę Employee, która będzie korzystać z tych danych:
package pl.devfoundry.newio.demo.domain;
public class Employee {
private String firstName;
private String lastName;
private int age;
private boolean fullTime;
public Employee(String firstName, String lastName, int age, boolean fullTime) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.fullTime = fullTime;
}
@Override
public String toString() {
return "Employee{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
", fullTime=" + fullTime +
'}';
}
}
Skoro mamy już nasz plik oraz klasę wzorcową, czas zabrać się za odczyt z pliku. Od Javy 7 służą nam (w podstawowych przypadkach) dwie klasy – Path i File. Pierwsza z nich odpowiada za wszelkiego rodzaju ścieżki w naszym systemie – zarówno do plików, jak i katalogów. Nie operujemy więc już na czystych ścieżkach. File, jak łatwo wywnioskować, służy do przechowywania informacji o pliku. Z tymi dwoma klasami spokrewnione są dwie klasy „utilsowe” o nazwach (jak to w Javie się przyjęło) Paths i Files. Dzięki nim możemy w bardzo prosty sposób porównać ze sobą np. dwie ścieżki, pobierać plik z danej ścieżki, odczytywać zawartość pliku, sprawdzać prawa dostępowe i wiele innych. Nas jednak interesuje obecnie najprostszy przypadek użycia – czyli odczyt wszystkich linijek pliku. Kod rozwiązania będzie prezentował się tak:
package pl.devfoundry.newio.demo;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class MainApp {
public static void main(String[] args) throws IOException {
Path path = Paths.get("./employees.txt");
List lines = Files.readAllLines(path);
}
}
Używamy metody .get() z klasy Paths by utworzyć nową ścieżkę, która będzie wskazywać na plik employees.txt, który znajduje się w katalogu projektu. Następnie używamy klasy pomocniczej Files, by odczytać całą zawartość pliku podanego w ścieżce, linia po linii. I to tyle. Lista lines zawiera wszystkie linijki tekstu z pliku wejściowego. Czas więc na odrobinę logiki biznesowej i ich obróbkę.
package pl.devfoundry.newio.demo;
import pl.devfoundry.newio.demo.domain.Employee;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class MainApp {
public static void main(String[] args) throws IOException {
Path path = Paths.get("./employees.txt");
List lines = Files.readAllLines(path);
List employees = new ArrayList<>();
for(int i=0;i
Czyli wyszukujemy linijkę z napisem „EmployeeData”, a następnie pobieramy dane z trzech kolejnych i na ich podstawie tworzymy nowego pracownika. Oczywiście nie ma tutaj żadnej kontroli czy dane są odpowiednie i czy nie przekroczymy indeksu – w aplikacji produkcyjnej taka walidacja musiałaby się oczywiście pojawić. Tutaj dla zachowania większej przejrzystości po prostu przechodzimy dalej.
NewIO 2 – zapis do pliku
Po pierwsze potrzebujemy logiki, która nasz obiekt przerobi na format JSON. W 99% przypadków takie rzeczy robi się za pomocą bibliotek zewnętrznych, jednak tu mamy prosty przypadek, dla którego nie warto dociągać całych bibliotek, skoro wystarczy jedna prosta metoda toJson() dla obiektu i druga dla całej listy. Do klasy Emplyee dodajemy
public String toJson() {
return "{" +
"\"firstName\": \"" + this.firstName + "\", " +
"\"lastName\": \"" + this.lastName + "\", " +
"\"age\":" + this.age + ", " +
"\"fullTime\": " + Boolean.toString(this.fullTime) +
"}";
}
Natomiast do klasy MainApp dorzucamy metodę statyczną
public static String toJson(List employees) {
String empl = employees.stream()
.map(Employee::toJson)
.collect(Collectors.joining(","));
return "{\"employees\": ["+empl+"]}";
}
Tak oto za pomocą kilku linijek kodu, bez użycia i narzutu zewnętrznych bibliotek, mamy nasze dane w formacie JSON. Jedyne, co pozostało, to zapisać je do pliku. Na szczęście dla nas Java 7 również bardzo ułatwiła sprawę. Przez „bardzo” mam na myśli fakt, że całą tę operację zmieścimy w dwóch linijkach:
Path savePath = Paths.get("./employees.json");
Files.write(savePath,toJson(employees).getBytes(Charset.forName("UTF-8")));
W pierwszej z nich tworzymy (nieistniejącą jeszcze) ścieżkę do pliku i ponownie używamy klasy Files, tym razem by zapisać nasz plik. Zawsze warto do metody .getBytes() dorzucić kodowanie, w jakim chcemy zapisać nasze dane – by uniknąć potencjalnych szlaczków zamiast np. polskich znaków.
Podsumowanie
Jak powyższe przykłady pokazują – Java 7 bardzo wiele zmieniła, jeśli chodzi o obsługę plików. Wszystko sprowadza się do tego, że w końcu można zapamiętać jak wczytać i zapisać plik, co przy wcześniejszych rozwiązaniach było dla mnie niemożliwością
i zawsze powodowało wspomaganie się StackOverflow 
Kod rozwiązania jest dostępny na platformie github.
The post Zapis i odczyt plików w Java 7+ appeared first on Dev Foundry.
]]> Czytaj dalej...Sortowanie kolekcji w Javie
The post Sortowanie kolekcji w Javie appeared first on Dev Foundry.
]]>Comparable od sortowania z wykorzystaniem interfejsu Comparator i w jaki sposób z nich korzystać. Pokażę też dwie kolekcje, których działanie opiera się na zachowaniu odpowiedniej kolejności elementów: TreeSet oraz TreeMap.
Kiedy mówimy o sortowaniu danej kolekcji, to najczęściej chodzi nam o skorzystanie z metody sort lub reverse, które są dostępne w klasie Collections, w paczce java.util. Metoda sort sortuje daną kolekcję rosnąco według jakiegoś kryterium, natomiast metoda reverse odwraca kolejność elementów w kolekcji.
Przyjrzyjmy się tym metodom na prostym przykładzie listy Integerów:
Listprices = Arrays.asList(1, 5, 32, 76, 3, 21, 7, 333, 43, 221); System.out.println("Nieposortowane: " + prices); Collections.sort(prices); System.out.println("Posortowane rosnąco: " + prices); Collections.reverse(prices); System.out.println("Posortowane malejąco: " + prices);
Tworzymy listę liczb całkowitych, wypisujemy na ekran najpierw nieposortowane liczby, następnie korzystamy z metody sort i wypisujemy elementy kolekcji na ekran – będą one posortowane rosnąco. Następnie wywołujemy metodę reverse i znowu wypisujemy liczby na ekran. Tym razem będą one posortowane malejąco:
Nieposortowane: [1, 5, 32, 76, 3, 21, 7, 333, 43, 221] Posortowane rosnąco: [1, 3, 5, 7, 21, 32, 43, 76, 221, 333] Posortowane malejąco: [333, 221, 76, 43, 32, 21, 7, 5, 3, 1]
Oczywiście tyczy się to nie tylko liczb całkowitych, ale również Stringów i ogólnie tak zwanych wrapperów typów prymitywnych, czyli klas typu Integer, Double, Long, Float, etc. W ten sposób możemy również sortować kolekcję przechowującą typy enumowe.
Z pomocy metod sort i reverse możemy skorzystać w każdej kolekcji, która implementuje interfejs List.
Sortowanie obiektów, podobnie jak w przypadku nadpisywania metody equals, jest subiektywne. To od nas zależy według czego będziemy dany obiekt sortować. Możemy bowiem zdecydować się na posortowanie po wartości jednego lub więcej pól. Spójrzmy na przykład akcji na giełdzie:
public class Stock {
private String name;
private String symbol;
private double price;
public Stock(String name, String symbol, double price) {
this.name = name;
this.symbol = symbol;
this.price = price;
}
public String getName() {
return name;
}
public String getSymbol() {
return symbol;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "Stock{" +
"name='" + name + '\'' +
", symbol='" + symbol + '\'' +
", price=" + price +
'}';
}
}
Klasa posiada trzy pola prywatne, konstruktor, gettery oraz metodę toString, która pozwoli na ładniejsze formatowanie wyświetlanych obiektów tej klasy na standardowym wyjściu.
Gdybyśmy teraz stworzyli kolekcje przechowującą elementy typu Stock i spróbowali na niej wywołać metodę sort, to niestety w linii 11 otrzymamy błąd kompilacji:
Stock stock1 = new Stock("cd projekt blue", "cdp", 15.78);
Stock stock2 = new Stock("amazon", "amz", 5.23);
Stock stock3 = new Stock("microsoft", "mst", 157.65);
Stock stock4 = new Stock("google", "ggl", 7.08);
Stock stock5 = new Stock("apple", "apl", 66.43);
List stockList = Arrays.asList(stock1, stock2, stock3, stock4, stock5);
stockList.forEach(System.out::println);
Collections.sort(stockList);
Nie powinno nas to dziwić: w końcu według jakiego kryterium ta kolekcja miałaby być posortowana? Klasa Stock posiada 4 pola, tak naprawdę możemy sortować listę po każdym z tych pól i w każdym przypadku będzie miało to merytoryczny sens.
Zatem co zrobić? Tutaj pomoże nam właśnie interfejs Comparable.
W interfejsie Comparable znajduje się deklaracja metody compareTo:
public int compareTo(T o);
Załóżmy, że porównujemy obiekty A i B tego samego typu i na obiekcie A wywołujemy metodę compareTo: A.compareTo(B).
Z opisu metody wynika, że implementacja metody compareTo powinna zwrócić:
A uznamy za „mniejszy” od obiektu B,A uznamy za „większy” od obiektu B,A uznamy za równy obiektowi B.Zaimplementujmy zatem metodę compareTo w klasie Stock. Załóżmy, że chcemy sortować akcje według ich ceny.
Najpierw dodajemy odpowiednią frazę do deklaracji klasy:
public class Stock implements Comparable{
Teraz czas na implementację metody compareTo. Można to zrobić na kilka sposobów. Najbardziej oczywisty to coś w rodzaju:
@Override
public int compareTo(Stock o) {
if(this.getPrice() < o.getPrice()) return -1;
if(this.getPrice() > o.getPrice()) return 1;
else return 0;
}
Taki zapis można nieco uprościć wykorzystując różnicę matematyczną (z dodatkiem rzutowania w tym konkretnym przypadku):
@Override
public int compareTo(Stock o) {
return (int) (this.getPrice() - o.getPrice());
}
Kolejnym sposobem jest skorzystanie z metody compare dostępnej w każdym wrapperze typów prymitywnych, o których wspominałem wcześniej. W tym przypadku możemy skorzystać z metody statycznej dostępnej w klasie Double:
@Override
public int compareTo(Stock o) {
return Double.compare(this.getPrice(), o.getPrice());
}
Skoro mamy już implementację interfejsu Comparable, to możemy teraz sprawdzić czy zadziała nam sortowanie z metodą sort(tam gdzie poprzednio mieliśmy błąd kompilacji):
Stock{name='amazon', symbol='amz', price=5.23}
Stock{name='google', symbol='ggl', price=7.08}
Stock{name='cd projekt blue', symbol='cdp', price=15.78}
Stock{name='apple', symbol='apl', price=66.43}
Stock{name='microsoft', symbol='mst', price=157.65}
Jak widać wszystko działa jak należy: obiekty są posortowane rosnąco według ceny.
Jednak co zrobić w sytuacji, gdy akcje będą miały taką samą cenę i wtedy w drugiej kolejności chcielibyśmy posortować je według kolejności alfabetycznej po nazwach? A co jeśli nazwy również będą takie same (to się pewnie nie zdarzy na prawdziwej giełdzie :P) i wówczas chcielibyśmy je posortować po symbolach? Nie ma problemu, musimy po prostu dodać do implementacji metody compareTo stosowną logikę:
@Override
public int compareTo(Stock o) {
int compareResult = Double.compare(this.getPrice(), o.getPrice());
if(compareResult == 0){
compareResult = this.getName().compareToIgnoreCase(o.getName());
}
if(compareResult == 0){
compareResult = this.getSymbol().compareToIgnoreCase(o.getSymbol());
}
return compareResult;
}
Najpierw porównujemy cenę i wynik przypisujemy do zmiennej compareResult. Następnie sprawdzamy czy compareResult jest równa 0. Jeśli tak, to oznacza to, że obydwa obiekty mają taką samą cenę, więc chcemy je następnie porównać po nazwie. Porównujemy je zatem po nazwie (tym razem korzystając z metody dostępnej w klasie String: compareToIgnoreCase, która dokona porównania bez względu na wielkość znaków) i ponownie sprawdzamy czy compareResult jest równa 0. Jeśli tak, to zarówno cena, jak i nazwa akcji jest taka sama. Musimy zatem dokonać trzeciego porównania, tym razem symboli. Są one typu String, więc ponownie korzystamy z metody compareToIgnoreCase. Na końcu zwracamy wartość zmiennej compareResult.
W rezultacie, przy drobnej zmianie wartości naszych przykładowych obiektów (tak aby zarówno ceny, jak i nazwy akcji się powtarzały):
Stock stock1 = new Stock("cd projekt blue", "cdp", 5.23);
Stock stock2 = new Stock("amazon", "amz", 5.23);
Stock stock3 = new Stock("amazon", "aat", 5.23);
Stock stock4 = new Stock("google", "ggl", 7.08);
Stock stock5 = new Stock("apple", "apl", 7.08);
otrzymujemy kolekcję akcji poprawnie posortowaną najpierw po cenie, następnie po nazwie i na końcu po symbolu.
Przyszedł czas na poznanie pewnego ułatwienia. Jest nim klasa CompareToBuilder, dzięki której możemy w bardzo prosty sposób zaimplementować metodę compareTo. Klasa ta znajduje się w paczce org.apache.commons.lang3.builder, więc żeby móc z niej skorzystać, należy dodać odpowiednią zależność do pliku pom.xml. Jak się można domyślić, klasa ta jest implementacją wzorca projektowego Builder.
Spójrzmy teraz w jaki sposób prezentowałaby się metoda compareTo, gdybyśmy chcieli ją zaimplementować przy użyciu CompareToBuildera, mając te same wymagania co w powyższym przykładzie (sortowanie po cenie, nazwie i symbolu):
@Override
public int compareTo(Stock o) {
return new CompareToBuilder()
.append(this.getPrice(), o.getPrice())
.append(this.getName(), o.getName())
.append(this.getSymbol(), o.getSymbol())
.build();
}
Moim zdaniem wygląda świetnie: prosto i przede wszystkim przejrzyście. Wykonujemy tutaj dość powszechne chainowanie metod. Korzystamy z konstruktora, aby utworzyć obiekt typu CompareToBuilder, używamy metod append, aby dodać kolejne pola według których chcemy sortować obiekty (może to być jedno lub więcej pól) i na końcu wywołujemy metodę build. Całą resztę załatwia za nas CompareToBuilder.
Kolejnym sposobem na sortowanie obiektów w kolekcji jest skorzystanie z interfejsu Comparator. Możemy to zrobić tworząc odrębną klasę, na przykład StockComparator:
public class StockComparator implements Comparator{ @Override public int compare(Stock o1, Stock o2) { return new CompareToBuilder() .append(o1.getPrice(), o2.getPrice()) .append(o1.getName(), o2.getName()) .append(o1.getSymbol(), o2.getSymbol()) .build(); } }
Jest to dość prosta implementacja: tworzymy nową klasę, implementujemy interfejs Comparator (który przyjmuje typ generyczny – podajemy tam oczywiście klasę Stock) i nadpisujemy metodę compare, której sposób działania powinien być identyczny jak metody compareTo z interfejsu Comparable – zatem tu również możemy skorzystać z klasy CompareToBuilder.
Teraz podczas sortowania możemy podać instancję klasy StockComparator jako drugi argument metody sort:
Collections.sort(stockList, new StockComparator());
Możemy również utworzyć instancję klasy typu Comparator jako anonimową klasę wewnętrzną, czyli w żądanym miejscu w kodzie tworzymy od razu instancję oraz definicję całej klasy. Tak utworzoną instancję przekazujemy następnie do metody sort i w pętli wypisujemy elementy kolekcji stockList na ekran:
ComparatorstockComparator = new Comparator () { @Override public int compare(Stock o1, Stock o2) { return new CompareToBuilder() .append(o1.getPrice(), o2.getPrice()) .append(o1.getName(), o2.getName()) .append(o1.getSymbol(), o2.getSymbol()) .build(); } }; Collections.sort(stockList, stockComparator); stockList.forEach(System.out::println);
Comparator jest interfejsem funkcjonalnym, więc możemy skorzystać z wyrażenia lambda, aby jeszcze bardziej skrócić ten zapis:
ComparatorstockComparator = (o1, o2) -> new CompareToBuilder() .append(o1.getPrice(), o2.getPrice()) .append(o1.getName(), o2.getName()) .append(o1.getSymbol(), o2.getSymbol()) .build();
Kolejną możliwością, jaką daje nam Java 8 i jej nowsze wersje, jest użycie Comparatora w metodzie interfejsu Stream – sorted:
ListsortedStocks = stockList.stream() .sorted(stockComparator) .collect(Collectors.toList()); sortedStocks.forEach(System.out::println);
Jeśli jesteśmy przy strumieniach, to warto poznać dwie przydatne metody z interfejsu Comparator: comparing i thenComparing. Możemy z nich skorzystać używając wywoływań łańcuchowych oraz referencji metod, a wszystko to w obrębie metody stream.sorted:
ListsortedStocks = stockList.stream() .sorted(Comparator .comparing(Stock::getPrice) .thenComparing(Stock::getName) .thenComparing(Stock::getSymbol)) .collect(Collectors.toList());
W przypadku Comparatora możemy również skorzystać z metody sort dostępnej bezpośrednio w interfejsie List – jest to alternatywa dla metody Collections.sort, która jako argument przyjmuje właśnie klasę typu Comparator:
stockList.sort(stockComparator);
Klasa typu Comparator będzie szczególnie pomocna gdy:
compareTo. Wówczas możemy utworzyć osobną klasę typu Comparator lub utworzyć anonimową klasę wewnętrzną w stosownym miejscu,W Javie istnieją również dwie kolekcje, których działanie opiera się między innymi na sortowaniu elementów: TreeSet oraz TreeMap.
Kolekcja TreeSet to zbiór, który implementuje interfejs SortedSet. Elementy tej kolekcji będą sortowane według ich naturalnej kolejności (np. w przypadku elementów typu Integer, będą one uporządkowane rosnąco) lub w kolejności podanej przy tworzeniu zbioru. Mamy dwie możliwości określenia kolejności sortowania elementów:
compareTo w rzeczonej klasie,compare.Przykład tych dwóch implementacji kolekcji TreeSet:
Bez Comparatora w konstruktorze – w tym przypadku klasa Stock musi implementowac interfejs Comparable i nadpisywać metodę compareTo:
TreeSetstockTreeSet1 = new TreeSet<>(); stockTreeSet1.add(stock1); stockTreeSet1.add(stock2); stockTreeSet1.add(stock3); stockTreeSet1.add(stock4); stockTreeSet1.add(stock5); stockTreeSet1.forEach(System.out::println);
Oraz z przekazaną instancją klasy typu Comparator w konstruktorze (korzystamy z utworzonej wcześniej instancji klasy typu Comparator i przekazujemy ją w konstruktorze):
TreeSetstockTreeSet2 = new TreeSet<>(stockComparator); stockTreeSet2.add(stock1); stockTreeSet2.add(stock2); stockTreeSet2.add(stock3); stockTreeSet2.add(stock4); stockTreeSet2.add(stock5); stockTreeSet2.forEach(System.out::println);
Dość podobnie sprawy mają się w przypadku kolekcji TreeMap. Jest to mapa implementująca interfejs SortedMap. Jej klucze sortowane są według naturalnej kolejności, chyba że kluczami są obiekty klasy, która implementuje interfejs Comparable lub jeśli przy deklaracji mapy, w konstruktorze podamy Comparator:
TreeMapstockStringTreeMap = new TreeMap<>(stockComparator); stockStringTreeMap.put(stock1, "stock 1"); stockStringTreeMap.put(stock2, "stock 2"); stockStringTreeMap.put(stock3, "stock 3"); stockStringTreeMap.put(stock4, "stock 4"); stockStringTreeMap.put(stock5, "stock 5"); stockStringTreeMap.keySet().forEach(System.out::println);
Jak wynika z powyższego wpisu – jest wiele różnych sposobów na sortowanie elementów kolekcji w Javie. To jakiego wyboru dokonamy, będzie najczęściej determinowane przez projekt w którym pracujemy, obiekty z jakimi mamy do czynienia, wydajność danego rozwiązania lub jeszcze inne czynniki. Najważniejsze żeby zdawać sobie sprawę z wachlarza dostępnych rozwiązań. Wówczas nic nie będzie nas w stanie zaskoczyć.
Kod z przykładami do powyższego wpisu znajdziesz na githubie.
The post Sortowanie kolekcji w Javie appeared first on Dev Foundry.
]]>