#06 — Praca z kolekcjami

By 18 September 2014 May 20th, 2015 Kurs Javy

W poprzed­niej lekcji utworzyliśmy kolekcję ele­men­tów, a następ­nie dodal­iśmy do niej naszego nowo utwor­zonego kota. Dzisi­aj poz­namy kil­ka sposobów, jak odczy­tać infor­ma­c­je zapisane w kolekcji lub jak z kolekc­ja­mi pracować.

Kolekc­je to jak już zostało powiedziane jeden z najwazniejszych ele­men­tów języ­ka Java. Dlat­ego tak ważne jest ich dobre poz­nanie, a także oswo­je­nie się z oper­ac­ja­mi które może­my na nich wykonać.

Lekcja

Dzisiejsza lekc­ja będzie obe­j­mowała dwa główne ele­men­ty — pętlę for oraz korzys­tanie z kolekcji typu lista (będziemy w przykładach uzy­wać ArrayListy jako przykładu, ale pamię­taj, że ist­nieją też inne listy — wszys­tkie je zna­jdziesz w doku­men­tacji Ora­cle, w sekcji ‘all known imple­men­ta­tions’ dla inter­fe­j­su List). Zaczniemy od samych kolekcji a następ­nie omówimy pętlę for i jak uży­wać jej z kolekcjami.

Kolekcje typu lista (java.util.List)

Lista to najczęś­ciej spo­tykana kolekc­ja w pra­cy z językiem Java. Nie zgłębi­a­jąc szczegółów tech­nicznych, listę moż­na opisać jako uporząd­kowany zbiór ele­men­tów, w którym może­my użyć dowol­nego z ele­men­tów (zarówno tego, który jest pier­wszy, jak i ostat­ni, ale też każdego pomiędzy nimi), może­my dodawać nowe ele­men­ty (bez lim­i­tu rozmi­aru — przy­na­jm­niej teo­re­ty­cznie) i usuwać te już ist­niejące. Może­my ją porów­nać do nieskończe­nie długiej szu­fla­dy w jakiejś szafce na doku­men­ty z amerykańs­kich filmów, w której mamy tecz­ki z jakim­iś doku­men­ta­mi — te tecz­ki to nasze ele­men­ty, a szu­fla­da to kolekc­ja. Może­my wyciągnąć dowol­ną z teczek, i wiemy ile ich jest.

Listy w Javie (jak wszys­tkie kolekc­je) są para­me­try­zowane, tzn. musimy wcześniej zadeklarować jakiego typu obiek­ty będziemy trzy­mali w tej liś­cie. Może­my oczy­wiś­cie powiedzieć ‘dowol­ny’ (jako typ wpisu­jąc Object) ale raczej tego nie chce­my. W poprzed­nim zada­niu uży­wal­iśmy listy kotów, w przykładach w dzisiejszej lekcji będziemy uży­wać ciągów znaków — przykłady dzię­ki temu będą krót­sze ;) Para­metr kolekcji — oznaczany w doku­men­tacji jako E — to w naszym przy­pad­ku będzie String.

Do tej pory uży­wal­iśmy już oper­acji dodawa­nia na listę, ale usys­tem­atyzu­je­my sobie naszą wiedzę o lis­tach i omówimy najważniejsze metody. Oczy­wiś­cie metod jest dużo więcej, z niek­tórych będzie potrze­ba sko­rzys­tać bard­zo szy­bko, innych nie będziesz uży­wała bard­zo dłu­go. Dlat­ego potrak­tuj to jedynie jako zachęte do zapoz­na­nia się z doku­men­tacją (link zna­jdziesz w tytule tego akapitu).

add (E element) — dodaj nowy element do kolekcji

Doda­je nowy ele­ment do kolekcji. Po tej oper­acji dłu­gość listy zwięk­sza się o jeden, nowo dodany ele­ment trafia na jej koniec. To najczęst­szy sposób dodawa­nia ele­men­tów do listy.

List<String> lista = new ArrayList<String>(); //lista pusta
lista.add("pierwszy"); //lista ma jeden element
lista.add("inny"); //lista ma dwa elementy
lista.add("pierwszy"); //lista ma trzy elementy

addAll (Collection<E> elementy) — dodaj wiele elementów do kolekcji

Ta meto­da pozwala za jed­nym razem dodać wiele ele­men­tów (nie uży­wa­jąc pętli, w zależnoś­ci od typu listy, oper­ac­ja ta powin­na być co najm­niej tak szy­b­ka jak dodawanie w pętli, najczęś­ciej jest dużo szyb­sza, co ma znacze­nie w przy­pad­ku, kiedy mamy naprawdę dużo ele­men­tów). Podob­nie jak w poprzed­niej metodzie, ele­men­ty trafi­a­ją na koniec listy, są dodawane w kole­jnoś­ci, w jakiej kolekc­ja ele­men­ty je zwraca — w poniższym przykładzie listy będą po pros­tu połac­zone a kole­jność ele­men­tów zostanie w nich zachowana.

List<String> listaMoja = ... ; //jakaś lista z 5 elementami
List<String> listaCzyjas = ... ; //jakaś lista z 7 elementami

List<String> lista = new ArrayList<String>(); //lista pusta
lista.addAll(listaCzyjas); //lista ma 7 elementów
lista.addAll(listaMoja); //lista ma 12 elementów

get (int indeks) — pobierz element  kolekcji

Pozwala pobrać ele­ment z dowol­nej pozy­cji na liś­cie, przy czym pier­wsza pozy­c­ja ma indeks 0 (jak wszędzie w Javie). Jeśli wskazana pozy­c­ja jest więk­sza niż ilość ele­men­tów na liś­cie, otrzy­mamy wyjątek (Index­Out­Of­Bound­Ex­cep­tion). Pamię­ta­jmy, że nawet usuwa­jąc ele­ment ze środ­ka, lista pozosta­je ciągła (tzn. ele­ment z kole­jnej pozy­cji zosta­je umies­zony na pozy­cji z której usuwamy).

List<String> lista = new ArrayList<String>(); //lista pusta
lista.add("pierwszy"); //lista ma jeden element
lista.add("inny"); //lista ma dwa elementy
lista.add("pierwszy"); //lista ma trzy elementy
String poczatek = lista.get(0); // zwraca "pierwszy"
String koniec = lista.get(2); // zwraca "pierwszy"
String srodek = lista.get(1); // zwraca "inny"

remove(int indeks) — usuń element z określonej pozycji

Meto­da ta pozwala usunąć dowol­ny ele­ment z dowol­nej pozy­cji na liś­cie. Jeśli ele­ment nie ist­nieje, może­my otrzy­mać wyjątek taki sam, jak w przy­pad­ku pobiera­nia ele­men­tu z listy (może­my także w pewnych szczegól­nych przy­pad­kach otrzy­mać wyjątek Unsup­port­e­d­Op­er­a­tionEx­cep­tion, jeśli uży­ty przez nas typ listy nie pozwala usuwać ele­men­tów, ale jest to bard­zo rzad­ka sytu­ac­ja). Meto­da ta poza usunię­ciem ele­men­tu z listy, zwraca go (może­my więc coś z nim dalej robić)

List<String> lista = new ArrayList<String>(); //lista pusta
lista.add("pierwszy"); //lista ma jeden element
lista.add("inny"); //lista ma dwa elementy
lista.add("pierwszy"); //lista ma trzy elementy
String poczatek = lista.get(0); // zwraca "pierwszy"
poczatek = lista.remove(0); //zwraca "pierwszy"
String srodek = lista.get(0); // zwraca "inny"
String koniec = lista.get(1); // zwraca "pierwszy"

isEmpty() — sprawdź, czy na liście są jakies elementy

Meto­da zwraca wartość prawda/fałsz, która mówi nam czy lista jest pus­ta (tzn. czy ma dokład­nie zero elementów).

List<String> lista = new ArrayList<String>(); //lista pusta
boolean pusta = lista.isEmpty() //zwraca true
lista.add("pierwszy"); //lista ma jeden element
pusta = lista.isEmpty() //zwraca false
lista.remove(0); //zwraca "pierwszy", lista ma 0 elementów
pusta = lista.isEmpty() //zwraca true

size() — sprawdź, ile elementów jest na liście

Meto­da ta pozwala na sprawdze­nie ile ele­men­tów zna­j­du­je się na liś­cie. Jest ona szczegól­nie użytecz­na, jes­li wczy­tu­je­my od użytkown­i­ka np. numer ele­men­tu z listy i chce­my się upewnić, że użytkown­ik podał praw­idłową wartość (dzię­ki czemu unikniemy koniecznoś­ci obsługi­wa­nia wyjątków w naszym kodzie).

List<String> lista = new ArrayList<String>(); //lista pusta
int ilosc = lista.size(); //zwraca 0
lista.add("pierwszy"); //lista ma jeden element
ilosc = lista.size(); //zwraca 1
lista.add("inny"); //lista ma dwa elementy
ilosc = lista.size(); //zwraca 2
lista.add("pierwszy"); //lista ma trzy elementy
ilosc = lista.size(); //zwraca 3
lista.remove(0); //zwraca "pierwszy"
ilosc = lista.size(); //zwraca 2

Pętla for

W Javie ist­nieją dwa wari­anty pętli for (nazwy zmyślone, na potrze­by rozróż­ni­a­nia w ramach tej lekcji, nie są to ofic­jalne nazwy obu wari­antów!) — pier­wszy, nor­mal­ny (‘stan­dar­d­owy’) oraz dru­gi, iter­a­cyjny (w innych językach częs­to pęt­la ta ma nazwę fore­ach, za chwilę dowiesz się dlaczego; cza­sem moż­na spotkać się z nazwą ‘for-each’).

Standardowa pętla for

Stan­dar­d­owa pęt­la for ma w języku Java następu­jącą składnię:

for (inicjalizacja; warunek_zakończenia; krok) {
//to, co chcemy w pętli robić
}

W pier­wszej częś­ci mamy następu­jące ele­men­ty (każdy z nich jest opcjonalny):

  • inic­jal­iza­c­ja — ten frag­ment wykona się tylko raz, przed pier­wszą iter­acją. Moż­na tutaj np. zainicjować zmi­en­ną pomoc­niczą, której będziemy uży­wali do tego, żeby pęt­la wykon­ała się dokład­nie X razy
  • warunek_zakończenia — ten frag­ment jest wykony­wany przed każ­da iter­acją. Jeśli warunek tutaj opisany jest spełniony, to następ­na iter­ac­ja się wykonu­je, jeśli nie to pęt­la kończy działanie
  • krok — ten frag­ment wykonu­je się po każdej iter­acji, uży­wamy go np. do inkre­men­towa­nia (pow­ięk­szenia o jeden) liczni­ka iteracji

Przykład­owa pęt­la for wyglą­da następująco:

for (int i = 0; i<10; i++) {
System.out.println("Iteracja: " + i);
}

Powyższa pęt­la wyp­isze dziesięć lin­i­jek. Nasza pęt­la for:

  • przed iter­ac­ja­mi inic­jal­izu­je­my zmi­en­ną i i nada­je­my jej wartość 0
  • przed każdą iter­acją, sprawdza­my czy i jest mniejsze od 10 (zwróć uwagę, że sprawdza­my czy jest mniejsza od 10 — czyli dla i==10, pęt­la zostanie prz­er­wana; zmi­en­na i jest inicjowana wartoś­cią 0, więc zan­im pęt­la będzie prz­er­wana, wykona się 10 iter­acji: 0,1,2,3,4,5,6,7,8,9)
  • po każdej iter­acji pow­ięk­sza­my i o 1 (i++ oznacza pow­ięk­sz i o jeden)
  • w każdej iter­acji wyp­isu­je­my do kon­soli tekst “Iter­ac­ja X” (gdzie X to kole­jno 0, 1, 2 itd)

Może­my oczy­wiś­cie pom­inąć wszys­tkie te ele­men­ty, otrzy­mu­jąc tym samym pętle nieskończoną:

for (;;;) {
//tutaj będziemy potrzebować warunku, który przerwie pętlę instrukcją sterującą break, albo zakończy działanie aplikacji
}

Pętle najczęś­ciej uży­wane są w połącze­niu z jakąś kolekcją — np. w sytu­acji kiedy chce­my wykon­ać oper­ację na każdym z ele­men­tów, albo np. zsumować pola wszys­t­kich ele­men­tów w jed­ną wartość. Wraca­jąc bliżej aplikacji, którą pisze­my, frag­ment kodu który sumu­je masę kotów w naszej bazie danych mógł­by wyglą­dać następująco:

Float masa = 0.0f;
List<Kot> koty = ... ; //tutaj inicjujemy i wypełniamy listę
for (int i = 0; i<koty.size(); i++) {
Kot kot = koty.get(i);
masa += kot.getMasa();
}
System.out.println("Wszystkie koty razem ważą w sumie: " + masa);

Jak widać jest to mało wygodne (dużo niepotrzeb­ne­go pisa­nia), zobaczmy więc, jak może­my to uprościć.

Pętla for-each

Ta kon­strukc­ja jest w mojej subiek­ty­wnej opinii jest bardziej użytecz­na od przed­staw­ionej powyżej, choć oczy­wiś­cie zależy to od sytu­acji i należy dobrze znać obie. Kon­strukc­ja ta jest ściśle pow­iązana z kolekc­ja­mi, ponieważ prze­chodzi ona po każdym ele­men­cie kolekcji (jest to skrót od powyższego kodu). Zaletą jest to, że w przy­pad­ku kolekcji nieu­porząd­kowanych (np. Set, Map w ogól­nym przy­pad­ku), nie musimy samodziel­nie obsługi­wać iter­a­to­ra (sposób na prze­jś­cie po wszys­t­kich ele­men­tach kolekcji, także nieu­porząd­kowanej). Przed­staw­iony na końcu poprzed­niej sekcji przykład moż­na zapisac jako:

List<Kot> koty = ... ; //tutaj inicjujemy i wypełniamy listę
for (Kot kot : koty) {
masa += kot.getMasa();
}

Jak widać, sam zapis jest bardziej czytel­ny i łatwiejszy do pra­cy z nim. Ogól­na kon­strukc­ja wyglą­da następująco:

Collection<E> collection = ... ; //tutaj jakaś kolekcja
for (E element : collection) {
//działania na obiekcie
}

Poza słowem kluc­zowym for deklaru­je­my jakiego typu ma być ele­ment pod­brany w każdej iter­acji, jak ma się in nazy­wać, a także jak nazy­wa się kolekc­ja, z której będziemy pobier­ać elementy.

Materiały dodatkowe / dokumentacja

  1. Opis pętli for na stron­ie Ora­cle (EN)

Zadanie

Dzisiejsze zadanie będzie nieco bardziej skom­p­likowane od poprzed­nich. Ale w końcu to już 6 lekc­ja ;) Dzisi­aj prze­r­o­bimy zupełnie nasz pro­gram tak, aby był on już funkcjon­al­ny. Dla ułatwienia na końcu zada­nia zna­jdziesz dia­gram akty­wnoś­ci (jak z niego sko­rzys­tać, przeczy­tasz we wpisie Ani).

Zadanie pole­ga na prze­r­o­bi­e­niu pro­gra­mu tak, aby na początku wyświ­et­lało się menu (najprost­sze: najpierw wyp­isz wszys­tkie dostęp­ne opc­je, a następ­nie wczy­taj od użytkown­i­ka jego wybór), w którym będziemy mogli wybrać jed­ną z opcji: dodaj kota, pokaż koty, zamknij pro­gram (wpisu­jąc odpowied­nio 1, 2 lub x z klaw­iatu­ry). Po wyko­na­niu danej czyn­noś­ci, wracamy do początku, czyli pokazu­je­my menu.

Pier­wsza opc­ja, dodaj kota, powin­na wywoły­wać ten kod, który napisal­iśmy do tej pory. Czyli wczy­tu­je­my po kolei wszys­tkie dane kota, po czym doda­je­my go do kolekcji poprzez nasz obiekt kotDAO.

Dru­ga opc­ja najpierw wyświ­etli wszys­tkie koty (tylko imiona) oraz ich iden­ty­fika­to­ry (numer kole­jny). Użytkown­ik powinien wpisać numer w kon­soli, po czym wybrany kot się przed­staw­ia lub wyświ­et­lamy infor­ma­c­je o błędzie, że takiego kota nie ma.

Trze­cia opc­ja od razu zamy­ka program.

Dia­gram akty­wnoś­ci do tego zada­nia zna­jdziesz poniżej. Krok oznac­zony innym kolorem to ten frag­ment, który już zro­bil­iśmy w poprzed­nich lekcjach.

Diagram pomocniczy do zadania.

Dia­gram pomoc­niczy do zadania.

Podpowiedzi

More »
  1. Zwróć uwagę, że wszys­tkie opc­je (poza wyjś­ciem z pro­gra­mu) wraca­ją do menu — to wskazu­je że warto użyć pętli nieskońc­zonej w tym miejs­cu lub spry­t­nego warunku
  2. Przed­staw kota musi pobier­ać z listy kota na odpowied­niej pozy­cji — pozy­c­ja ta musi być wyrażona liczbą. Pamię­taj, żeby # kota zamienić z ciągu znaków (to, co wczy­tamy od użytkown­i­ka) na liczbę (podob­nie jak w poprzed­nich lekc­jach z wagą)
  3. Należy też mieć na uwadze, że użytkown­ik może wpisać niepraw­idłowe dane — nie zapom­i­naj o wal­i­dacji w każdym miejs­cu, w którym pytasz o coś użytkownika

zip Pobierz rozwiązanie tego zadania

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przy­dat­ną, mamy małą prośbę: pol­ub nasz fan­page. Dzię­ki temu będziesz zawsze na bieżą­co z nowy­mi treś­ci­a­mi na blogu ( i oczy­wiś­cie, z nowy­mi częś­ci­a­mi kur­su Javy). Dzięki!