Kolekcje w Javie cz. 1 Programowanie obiektowe Jacek Sroka na podstawie materiałów Janusza Jabłonowskiego 8 kwietnia 2013
Po co są kolekcje? Po co są kolekcje? A po co są programy? Żeby manipulować danymi. Mało danych - mały kłopot. Ot, parę zmiennych. No może jeszcze jakiś rekord (czy struktura). Dużo danych - duży kłopot. Ale przecież są tablice. A w komputerze nie ma innej pamięci wewnętrznej niż ciąg bajtów - czyli tablica.
Tablice to nie wszystko Tablice są bardzo cenne. Ale mają wady, które czasem bardzo utrudniają korzystanie z tablic: Mają stały rozmiar. Są indeksowane tylko liczbami (w Javie tylko od 0). Są nieefektywne przy niektórych operacjach (np. wstawianie pomiędzy). Nie zapewniają utrzymywania pewnych pożądanych warunków (np. posortowania elementów). Dlatego praktycznie każdy współczesny język dostarcza kolekcję kolekcji. Przy okazji często wzbogaconą innymi pojęciami (np. Iterator). Kolekcje powinny być ogólne, więc w językach ze statycznym typowaniem wymagają typów uogólnionych.
Po co omawiać kolekcje na wykładzie z PO? No dobrze, ale czemu mówimy o nich na tym wykładzie? Czy wykład z Programowania obiektowego musi omawiać kolekcje? Nie. Nie są one częścią paradygmatu obiektowego. Czy wykład z programowania obiektowego powinien omawiać kolekcje? Tak, tak, tak! Przykład bardzo rozbudowanej hierarchii klas. Być może są to najczęściej na świecie używane hierarchie klas. 1 Warto poznać rozwiązania (nie wszystkie będziemy chwalić) z takiej hierarchii. Kolekcje manipulują obiektami - to tak jak programiści obiektowi. Poza tym, są po prostu bardzo przydatne! Są ciekawe i niebanalne. Uwaga: nie zawsze kolekcje są zrealizowane jako hierarchie klas (np. STL z C++ wbrew pozorom nie). 1 Są silni konkurenci, np. hierarchie widżetów z GUI.
Co ciekawego zobaczymy? Różne sposoby nakładania struktury na dane. Skorzystanie z hierarchii dziedziczenia, żeby ułatwić naukę i korzystanie. Programowania w kategoriach interfejsów a nie do klas. Bardziej abstrakcyjny/uniwersalny kod, łatwiejsze ponowne wykorzystanie. Uzyskiwanie różnych algorytmów przez podstawianie różnych konkretnych klas (np. stos lub kolejka przy obchodzeniu grafu). Przykład uniwersalnego opisu obchodzenia różnych przecież kolekcji Pętla dla każdego. 2 Wzorzec Iterator. Problemy i niebezpieczeństwa związane z kolekcjami - nie ma róży bez kolców. Co trzymamy w kolekcjach? equals a hashcode. Równoczesne modyfikacje. 2 Tu nie chodzi o bardzo radykalne poglądy polityczne, proszę zwrócić uwagę na użycie pochylonej czcionki.
Kolekcje Def. 1. 3 Kolekcja jest to obiekt służący do przechowywania (innych) obiektów, udostępniający mechanizmy pozwalające na wstawianie, przeglądanie i pobieranie przechowywanych obiektów, nie ma zadanego specyficznego dla siebie rozmiaru. Ostatni warunek oznacza, że Pary nie będziemy uważać za kolekcję (ale tablice już tak). Skrót: kolekcja będziemy mówić zarówno o obiektach-kolekcjach jak i o ich klasach, czy nawet ich interfejsach. 3 Nie jest to definicja w sensie matematycznym. I nie będzie twierdzenia o pełności.
Skąd się biorą kolekcje w Javie? Pakiet java. util 4 i Google Guava. Zatem na początku naszych programów warto dopisać 1 import java. u t i l. ; W świecie Javy dostarczony zestaw kolekcji nazywa się JCF (Java Collection Framework). W Javie jest wiele klas i interfejsów kolekcji, ale uwaga: ani dla klas, ani dla interfejsów nie są one drzewem (w obu przypadkach to las). Jest interfejs Collection. Ale nie wszystkie kolekcje go implementują (sic!). Nie będziemy omawiać wszystkich kolekcji (niektóre są bardzo specyficzne). 4 Oraz java. util.concurrent ale o nim nie będziemy mówić.
Interfejsy kolekcji
Interfejsy kolekcji Nie ma drzewa :(. Czemu w korzeniu większego drzewa jest coś innego niż Collection? Iterable 1 public interface I t e r a b l e <E>{ 2 I t e r a t o r <E> i t e r a t o r ( ) ; 3 } To cały interfejs Iterable! Oznacza kontrakt: zawartość obiektu - niekoniecznie kolekcji - da się przejść za pomocą pętli dla każdego. O interfejsie Iterator powiemy dalej.
Interfejs Collection Collection 1 public interface C o l l e c t i o n <E> 2 extends I t e r a b l e <E>{ 3 / /... 4 } Oczywiście interfejs uogólniony typem elementów kolekcji!
Interfejs Collection Badanie rozmiaru kolekcji 1 boolean isempty ( ) ; 2 i n t size ( ) ; isempty() bezpieczniejsze, size () ogólniejsze. 5 5 Typ int jest 32 bitowy, po przekroczeniu przez kolekcję Integer.MAX_VALUE metoda size daje Integer.MAX_VALUE.
Interfejs Collection wstawianie Wstawianie 1 boolean add (E e ) 2 boolean addall ( C o l l e c t i o n <? extends E> c ) add() wstawia do kolekcji pojedynczy element. Warunek końcowy: po wykonaniu add() wstawiany element jest w kolekcji. Uwaga: po wykonaniu add() rozmiar kolekcji nie musi się zwiększyć (dlaczego?). addall() dodaje do odbiorcy tego komunikatu kolekcję-parametr. Wynik obu tych metod mówi o tym, czy kolekcja się zmieniła. Jeśli wstawienie się nie da wykonać - wyjątek (jeden z kilku).
Interfejs Collection - wstawienie Czemu nagłówek addall() nie jest taki: 1 boolean addall ( C o l l e c t i o n <E> c ) Typy uogólnione nie są kowariantne zwn parametry (dlaczego - potem). Więc do kolekcji Osób, nie dodalibyśmy kolekcji Studentów. Nagłówek addall() można oczywiście zapisać bez dżokera 1 <E1 extends E> boolean addall ( C o l l e c t i o n <E1> c )
Interfejs Collection - badanie zawartości Czy dany obiekt jest w kolekcji? 1 boolean contains ( Object o ) 2 boolean c o n t a i n s A l l ( C o l l e c t i o n <?> c ) Wynikiem containsall () jest true, jeśli wszystkie obiekty z kolekcji c znajdują się w kolekcji-odbiorcy komunikatu. Co jednak znaczy cotains()? Wynikiem jest true wtw gdy dla choć jednego elementu e kolekcji zachodzi: 1 ( o== null? e== null : o. equals ( e ) ). mamy tu specjalną obsługę null Dokumentacja zezwala kolekcji na zgłoszenie wyjątku, gdy obiekt, o którego występowanie pytamy, ma typ niezgodny z kolekcją lub jest wartością null, a kolekcja na występowanie w niej tej wartości nie zezwala. Ale mamy tu coś znacznie ciekawszego...
Interfejs Collection - co to znaczy należeć do kolekcji? Co to znaczy należeć do kolekcji? Dosłowne rozumienie byłoby nieciekawe. Obiekt równy danemu ma należeć do kolekcji.
Interfejs Collection - co to znaczy należeć do kolekcji? 1 C o l l e c t i o n <Para< String, String >> k o l = new A r r a y L i s t < > ( ) ; 2 Para< String, String > p1 = new Para <>( " Ala ", " Ola " ) ; 3 Para< String, String > p2 = new Para <>( " Ala ", " Ola " ) ; 4 Para< String, String > p3 = new Para <>( " Ula ", " Ela " ) ; 5 k o l. add ( p1 ) ; 6 System. out. p r i n t l n ( " Test zawierania : " + k o l. contains ( p1 ) 7 + ", " + k o l. contains ( p2 ) + ", " + k o l. contains ( p3 ) ) ; Test zawierania: true, true, false
Interfejs Collection - usuwanie z kolekcji Dwie operacje 1 boolean remove ( Object o ) 2 boolean removeall ( C o l l e c t i o n <?> c ) Usunięcie elementów równych wskazanym. Wynik (w obu metodach) czy kolekcja się zmieniła. Znaczenie oczywiste, ale... remove() usuwa jedno wystąpienie. Specyfikacja nie określa które to wystąpienie. removeall() usuwa wszystkie wystąpienia elementów kolekcji-parametru. Specyfikacja podaje, że po removeall() nie ma elementów należących do obu kolekcji.
Interfejs Collection - usuwanie z kolekcji - przykład 1 C o l l e c t i o n <Para< String, String >> k o l = new A r r a y L i s t < > ( ) ; 2 C o l l e c t i o n <Para< String, String >> kol2 = new A r r a y L i s t < > ( ) ; 3 Para< String, String > p1 = new Para <>( " Ala ", " Ola " ) ; 4 Para< String, String > p2 = new Para <>( " Ala ", " Ola " ) ;
Interfejs Collection - usuwanie z kolekcji - przykład 1 k o l. add ( p1 ) ; 2 k o l. add ( p1 ) ; 3 k o l. remove ( p2 ) ; 4 System. out. p r i n t l n ( " Usuwanie pojedynczego elementu : " 5 + k o l. size ( ) ) ; 6 k o l. add ( p1 ) ; 7 k o l. add ( p1 ) ; 8 kol2. add ( p2 ) ; 9 k o l. removeall ( kol2 ) ; 10 System. out. p r i n t l n ( " Usuwanie wszystkich elementów: " 11 + k o l. s ize ( ) ) ; Test usuwania pojedynczego elementu: 1 Test usuwania wszystkich elementów: 0
Pętla dla każdego Pętla do przeglądania kolekcji (dokładniej: tego co implementuje Iterable ). Składnia (nieco uproszczona) 1 for ( Typ I d e n t y f i k a t o r : Wyraż enie ) 2 I n s t r u k c j a Przykład 1 for ( S t r i n g s : k o l ) 2 System. out. p r i n t l n ( s ) ; Jak widać pętla foreach nie zawiera słowa foreach.
Iterator Iterator to obiekt pozwalający poruszać się (iterować) po innym obiekcie (kolekcji). Wzorzec projektowy Iterator. Czy jest potrzebny?
Alternatywy wobec iteratora 1 Metoda dająca inną kolekcję (albo tablicę). Wady Pamięć, czas. Znów potrzeba iteratora.
Alternatywy wobec iteratora 2a Iterator wbudowany z indeksowaniem 1 public E d a j E l t ( i n t i ) ; / / daj element o podanym indeksie Wady Użytkownik musi pamiętać bieżącą pozycję przeglądania. Dla efektywnego działania wymaga dostępu bezpośredniego, a to nie każda kolekcja może zapewnić.
Alternatywy wobec iteratora 2b Iterator wbudowany z dodatkowym typem Pozycja 1 public E d a j E l t ( Pozycja poz ) ; 2 / / daj element znajdujący się za pozycji poz 3 public Pozycja nast ( Pozycja poz ) ; 4 / / przesuń bieżącą pozycję do następnego elementu 5 / / za poz (dla poz == null przesuń na początek). 6 / / uwaga: po wywołaniu dla ostatniej pozycji wynikiem jest null. Wady Użytkownik musi pamiętać bieżącą pozycję przeglądania. Wymaga dodatkowego typu Pozycja.
Alternatywy wobec iteratora 2c Iterator wbudowany z klasycznym interfejsem. 1 public void napoczą tek ( ) ; 2 / / ustaw iterator na początek kolekcji 3 public boolean j e s t E l t ( ) 4 / / sprawdź, czy jest jeszcze jakiś element do obejrzenia 5 public E d a j E l t ( ) ; 6 / / daj kolejny element i przesuń bieżącą pozycję 7 / / iteratora o jeden element do przodu Wady Rozbudowany interfejs (3 operacje). Nie nadaje się do jednoczesnego wielokrotnego przechodzenia przez kolekcję.
Alternatywy wobec iteratora 3 Pętla dla każdego. Wady Ona właśnie korzysta z iteratora :).
Iterator - wreszcie Bardzo prosta hierarchia klas.
Iterator Interfejs Iterator 1 public interface I t e r a t o r <E> { 2 boolean hasnext ( ) ; 3 E next ( ) ; 4 void remove ( ) ; 5 } Znaczenie hasnext() i next() intuicyjne. Czemu nie ma reset ()? Po co - tworzymy nowy iterator! Czemu jest remove()? A jak inaczej wskazać miejsce do usunięcia? Usuwa element ostatnio przekazany przez next(). Wyjątek, jeśli nie było przedtem wołane next() - np. nie można dwa razy pod rząd wywołać remove(). Opcjonalna - nie każdy iterator implementuje.
ListIterator Pomijamy.
Pętla dla każdego raz jeszcze Zaimplementujmy! Składnia 1 for ( Typ I d e n t y f i k a t o r : Wyraż enie ) 2 I n s t r u k c j a Znaczenie 1 for ( I f v = Wyraż enie. i t e r a t o r ( ) ; f v. hasnext ( ) ; ) { 2 Typ I d e n t y f i k a t o r = f v. next ( ) ; 3 I n s t r u k c j a ; 4 } I jest typem iteratora dla wartości Wyrażenia, a fv jest świeżą zmienną. Jest druga wersja tej pętli - dla tablic. Czyli można wykonać pętlę dla każdego na obiekcie, który nie musi być kolekcją!
Tyle pracy na nic Tyle slajdów a żadnej konkretnej kolekcji :(. Będą za tydzień. Nas interesuje bardziej organizacje tak bogatego zestawu pojęć. Co dało zdefiniowanie interfejsów kolekcji? Strukturę pojęć - czyli porządek. Łatwość uczenia się kolekcji. Pozwala programować w kategoriach interfejsów (a nie klas). Czyli programować ładniej, ogólniej, uniwersalniej. Skorzystaliśmy już z tego przy definicji znaczenia pętli dla każdego. A na deser...
Piękno w informatyce Definicja metody addall() w kategoriach interfejsów. 6 1 public void addall ( C o l l e c t i o n <? extends T> k o l ) { 2 for ( T e l t : k o l ) 3 add ( e l t ) ; 4 } Porozkoszujmy się chwilę tym wspaniałym widokiem! Metoda, która dodaje nie-wiadomo-jaką kolekcję... nie-wiadomo-jakiego typu elementów... do nie-wiadomo-jakiej kolekcji... nie-wiadomo-jakiego typu elementów... w języku ze statyczną kontrolą typów... z gwarancją poprawności zwn typy... zdefiniowana raz dla całej, okazałej hierarchii kolekcji... wszystko w trzech (praktycznie) wierszach... siła abstrakcji jest wielka! 6 Dla uproszczenia pominęliśmy tu przekazywanie przez metody add() i addall() wyniku typu boolean
Podsumowanie Kolekcje są ważnym elementem każdego języka programowania. Warto definiować interfejsy i programować w ich kategoriach. Pojęcie interfejsu pozwala na wygodniejsze posługiwanie się kolekcjami. Do przeglądania ich zawartości przydaje się często pętla dla każdego.