Programowanie obiektowe II Kolekcje
Kolekcje – pakiet java.util Kolekcja to obiekt przechowujący dowolną ilość innych obiektów Kolekcja Obiekt 3 Obiekt 1 Obiekt 2
Rodzaje kolekcji Listy Zachowują kolejność Dopuszczają duplikaty Zbiory Nie zachowują kolejności Nie dopuszczając duplikatów Mapy Przechowują pary obiektów Klucze są unikatowe
Struktura biblioteki kolekcji Interfejsy Lista udostępnianych metod Dla użytkowników kolekcji Klasy abstrakcyjne Implementacja niektórych metod Dla twórców kolekcji Klasy konkretne Pełna implementacja Szczegóły na wazniak.mimuw.pl
Struktura biblioteki kolekcji Interfejsy Collection List Set Map Klasy abstrakcyjne AbstractCollection AbstractList AbstractSet AbstractMap Klasy konkretne ArrayList HashSet HashMap
Tworzenie i inicjowanie list Tworzenie – konstruktorem. Musimy też podać w nawiasach trójkątnych typ elementów ArrayList<String> lista = new ArrayList<String>(); Dodawanie elementów – metoda add: lista.add("Ela"); lista.add("Antek"); Pobranie elementu o indeksie i – metoda get: String imię = lista.get(i); Przeglądanie – pętla for each: for(String element : lista) System.out.println(element)
Klasy osłonowe Uwaga! W kolekcjach można przechowywać tylko obiekty ArrayList<int> lista; Jeśli chcemy przechowywać zmienne typów podstawowych, musimy je opakować w klasy opakowujące (osłonowe) ArrayList<Integer> lista; Typ podstawowy Klasa osłonowa int Integer double Double char Character boolean Boolean float Float
Klasy osłonowe ArrayList<Integer> lista = new ArrayList<Integer>(); Integer liczba = new Integer(123); lista.add(liczba); lista.add(new Integer(23)); System.out.println(lista); Integer a = lista.get(0); int b = lista.get(1).intValue();
Automatyczne opakowywanie i rozpakowywanie ArrayList<Integer> lista = new ArrayList<Integer>(); lista.add(123); lista.add(23); System.out.println(lista); int a = lista.get(0); int b = lista.get(1); Nie musimy się więc przejmować obecnością obiektów opakowanych. Tylko typ listy musi zawierać typ opakowujący
Lista ArrayList – przykład class Student{ List<Double> notes = new ArrayList<Double>(); public void addNote(double d){ if(d>1 && d<6) notes.add(d); } public double average(){ double sum = 0; for(double note : notes) sum += note; return sum/notes.size();
Metoda zwracająca „widok” Niektóre metody zwracają fragmenty kolekcji, np. metoda subList (podlista o podanym zakresie – indeks początkowy i końcowy): public List subList(int indexFrom, int indexTo) Te fragmenty kolekcji nie są nowymi niezależnymi obiektami, tylko są powiązane z macierzystą kolekcją List<Integer> list = new ArrayList<Integer>(); for(int i=0;i<10;i++) list.add(i); System.out.println(list); list.subList(3,8).clear();
Iteratory Iterator to specjalny obiekt służący do przemieszczania się po kolekcji Iterator posiada wskaźnik na bieżący element, na początku na pierwszy element Każda metoda next przesuwa wskaźnik o 1 element Można się przesuwać tylko raz od początku do końca (jeśli chcemy drugi raz, tworzymy nowy iterator) Scenariusz Wyciągamy iterator metodą iterator Sprawdzamy, czy jest dostępny następny element metodą hasNext Pobieramy element metodą next
Iteratory Pętla for each używa iteratora do przeglądania kolekcji ArrayList<String> lista = new ArrayList<>(); lista.add(„coś”); … Iterator<String> iter = lista.iterator(); while(iter.hasNext()){ String element = iter.next(); } Pętla for each używa iteratora do przeglądania kolekcji Metody remove i get również używają iteratora do znalezienia odpowiedniego elementu
Uwaga - jednoczesne modyfikowanie kolekcji Iterator<String> iter = list.iterator(); while(iter.hasNext()){ String element = iter.next(); if(element.equals(„John”)) list.remove(element); } ConcurrentModificationException Drugi iterator Iterator<String> iter = list.iterator(); while(iter.hasNext()){ String element = iter.next(); if(element.equals(„John”)) iter.remove(); }
Alternatywne implementacje list Klasa ArrayList korzysta z tablicy do przechowywania elementów Dostęp do elementu po indeksie jest szybki Dodawanie i usuwanie elementów może być wolne Klasa LinkedList przechowuje elementy w strukturze listy dwukierunkowej Dodawanie i usuwanie elementów jest szybkie Dostęp po indeksie może być wolny (trzeba przejść po kolejnych elementach, żeby dojść do szukanego) Obiekt1 Następny Poprzedni Obiekt2 Następny Poprzedni Obiekt3 Następny Poprzedni
Zmiana w klasie Student class Student{ List<Double> notes = new LinkedList<Double>(); public void addNote(double d){ if(d>1 && d<6) notes.add(d); } public double average(){ double sum = 0; for(double note : notes) sum += note; return sum/notes.size(); Jako typów warto używać zawsze interfejsów, wówczas zmiana obiektu implementującego jest łatwa (tylko w jednym miejscu)
Zbiory – kolekcja HashSet Zbiór to kolekcja, która: Nie wstawia duplikatów Nie zachowuje kolejności elementów (dlatego nie ma metody get, pozwalającej pobrać element po indeksie) Elementy zbioru muszą mieć dobrze zdefiniowaną metodę equals (służy ona do sprawdzania, czy wstawiany obiekt nie jest już przypadkiem umieszczony w zbiorze)
Przykład Set<Integer> set = new HashSet<Integer>(); set.add(1); System.out.println(set);
Zbiory posortowane - TreeSet Elementy takiej kolekcji muszą implementować Comparable lub być akceptowane przez komparator Dodatkowe metody interfejsu SortedSet: Widoki: subSet, headSet, tailSet Pierwszy i ostatni element: first, last Implementacja: TreeSet Set SortedSet HashSet TreeSet
Przykład z Comparable class Student implements Comparable<Student> { … public int compareTo(Student second) return name.compareTo(second.name); } Set<Student> set = new TreeSet<Student>(); set.add(new Student(„Kowalski”)); set.add(new Student(„Nowak”)); System.out.println(set);
Przykład z Comparator Jest to alternatywny (wobec interfejsu Comparable) sposób porównywania obiektów Stosuje się wtedy, gdy nie chcemy psuć standardowego sposobu porównywania obiektów, tylko dostarczyć nowy (także wtedy, gdy nie możemy modyfikować źródła klasy) Trzeba stworzyć klasę implementującą Comparator i przekazać obiekt tej klasy do konstruktora kolekcji TreeSet
Przykład z Comparator class StudentComparator implements Comparator<Student> { public int compare(Student first, Student second) double firstAvg = first.average(); double secAvg = second.average(); if(firstAvg > secAvg) return -1; else if(firstAvg < secAvg) return 1; return 0; } Comparator<Student> comp = new StudentComparator(); Set<Student> set = new TreeSet<Student>(comp); set.add(new Student(„Kowalski”)); set.add(new Student(„Nowak”)); System.out.println(set);
Jak zbiór przechowuje swoje elementy? 1 2 3 4 Tablica haszująca Umieszczenie obiektu: Obliczamy hashcode obiektu Umieszczamy obiekt na końcu listy w komórce o indeksie hashcode (modulo rozmiar tablicy) Pobranie elementu: Przeszukujemy listę z komórki o indeksie hashcode Jeśli metoda hashcode jest dobrze napisana, to listy są krótkie i umieszczanie i pobieranie elementów jest szybkie o1 o2 o3 o4 o5 o6
Funkcja haszująca Jest to metoda zdefiniowana w klasie Object (a więc dostępna w każdej klasie) Kontrakt dla funkcji haszującej: Wywołana na rzecz tego samego obiektu, zwraca to samo Jeśli 2 obiekty są równe (equals), to ich hashcody są równe Jeśli 2 obiekty są różne, to ich hashcody nie muszą być różne (ale jeśli są, to przyspieszy to działanie na tablicy haszującej) Funkcja haszująca jest dobra, jeśli rozmieszcza klucze równomiernie
Funkcja haszująca Jeśli umieszczamy własne obiekty w kolekcji HashSet (lub HashMap), to nasze obiekty muszą mieć dobrze napisaną metodę hashcode Zawsze kiedy piszemy własną wersję equals, powinniśmy też pisać hashcode Funkcja haszująca powinna używać wszystkich pól używanych przez equals
Przykład funkcji haszującej public int hashcode() { return year; } public int hashcode() { return name.hashcode(); } public int hashcode() { return name.hashcode()+17*year; }
Jeszcze inna implementacja zbioru: Zbiory powiązane LinkedHashSet Iterator zwraca elementy w kolejności dodania istnieje dodatkowa osobna lista powiązana przechowująca kolejność dodawania elementów 1 2 3 4 o1 o4 o3 o2 o6 o5 last first
Mapy – kolekcja HashMap Mapa to kolekcja, która przechowuje pary elementów postaci klucz-wartość Klucz musi być unikatowy (tzn. będzie tylko jedna wartość dla danego klucza) Wstawienie pary: metoda put Pobranie wartości dla danego klucza: metoda get Widoki: entrySet, keySet, values
Przykład Map<String,String> map = new HashMap<String,String>(); map.put(„kolor”,”zielony”); map.put(„rozmiar”,”mały”); System.out.println(map);
Mapy posortowane Elementy są sortowane po kluczach Klucze muszą być więc obiektami porównywalnymi (implementującymi Comparable lub akceptowanymi przez odpowiedni komparator) Implementacja: TreeMap