SlideShare a Scribd company logo
„Klasy kolekcji”
by Szymon Natanek
Techniki i języki programowania
Kolekcje obiektów
Tablice:
Klasa Arrays
Listy:
Klasy: ArrayList, LinkedList, Vector, Stack
Zbiory:
Klasy: TreeSet, HashSet, LinkedHashSet
Mapy:
Klasy: HashMap, LinkedHashMap, WeakHashMap, TreeMap,
IdentityHashMap, HashTable
Tablice
Tablica w Javie jest ciągiem obiektów (a raczej referencji do obiektów). Jest prostą
sekwencją liniową, pozwalającą na szybki dostęp do elementów.
Tablice posiadają ograniczenie rozmiaru. Można stworzyć tablicę o określonym
rozmiarze, ale nie można tego rozmiaru zmienić w czasie życia obiektu.
Aby uniknąć powyższego problemu, po zapełnieniu tablicy, można utworzyć nową, o
większym rozmiarze i skopiować zawartość poprzedniej do niej. Jest to jednakże
rozwiązanie mało wygodne.
Inicjowanie tablic:
MyObj[] tablica1; // niezainicjowana
MyObj tablica2[]; // jak wyżej
MyObj[] tablica3 = new MyObj[5] // zainicjowana tablica 5 elementowa
MyObj[] tablica4 ={new MyObj(), new MyObj()} /* zainicjowana tablica
2 elementowa */
Zaletą tablicy jest sprawdzanie zakresu, dlatego też nie możliwe jest wyjście poza
ściśle określony zakres tablicy, jak miało to miejsce w C lub C++
Tablice - ciąg dalszy
Tablicy niezainicjowanej nie można użyć. Jeżeli nastąpi odwołanie do
niezainicjowanej tablicy, kompilator powiadomi nas o błędzie:
int[] tablica1; // Tablica nie zainicjowana
int[] tablilca2 = new int[6];
int length = tablica1.length + tablica2.length; // Błąd
Tablica posiada składową tylko do odczytu length, będącą długością tablicy (lecz nie
obecnej ilości obiektów, tylko ilość obiektów jakie można umieścić).
W przeciwieństwie do klas kontenerowych, które przechowują referencje do
obiektów, tablice mogą przechowywać również zmienne typu podstawowego.
Tablice mogą być zwracane przez funkcje. W C lub C++ można było zwrócić
wskaźnik do tablicy jako wynik funkcji. Java natomiast daje możliwość zwracania całej
tablicy. Zwolnieniem pamięci z takiej tablicy nie trzeba się martwić, ponieważ po
zniknięciu wszystkich odwołań do niej, zostanie automatycznie usunięta.
Arrays
Klasa Arrays, zamieszczona w pakiecie java.util, posiada zestaw metod statycznych
do wykonywania operacji na tablicach:
equals(tablica1, tablica2); // Porównuje tablice pod względem równości
fill(tablica, wartość); // Wypełnia tablicę określoną wartością
sort(tablica, comparator); // Sortuje tablicę (comparator opcjonalny)
binarySearch(tablica, szukany_element, comparator ); /*Wyszukuje element
w tablicy posortowanej*/
Powyższe metody są przeciążone dla każdego z podstawowych typów i klasy Object
Ponadto klasa Arrays posiada metodę toList(), która zmienia tablicę w kontener List
Do kopiowania tablicy służy statyczna metoda System.arraycopy(), pozwalająca na
kopiowanie tablic, szybciej niż za pomocą własnej pętli for. Jest ona przeciążona tak, że
obsługuje tablice wszystkich typów.
Do porównywania elementów tablicy służy metoda compareTo() zamieszczona w
interfejsie java.lang.Comparable
Porównywanie elementów
Do porównywania elementów tablicy służy metoda compareTo() zamieszczona w
interfejsie java.lang.Comparable. Metoda ta jest niezbędna do działania metody sort()
Przykład 1 – porównanie z wykorzystaniem compareTo():
import java.util.*;
public class CompType implements Comparable {
int value;
...
public int compareTo(Object obj) {
int val = ((CompType)obj).value;
return (value < val ? –1 : (value == val ? 0 : 1));
}
Porównywanie elementów – ciąg dalszy
Przykład 2 – porównanie przy użyciu compare():
import java.util.*;
public class MyComp implements Comparable {
public int compare(Object o1, Object o2) {
int val = ((CompType)o1).value;
int val2 = ((CompType)o2).value;
return (val1 < val2 ? –1 : (val1 == val2 ? 0 : 1));
}
Wyszukiwanie elementów
Metody porównań wykorzystywane są również do wyszukiwania elementów za
pomocą binarySearch().
Przeszukiwanie tablic, jak sama nazwa sugeruje, odbywa się stosując wyszukiwanie
binarne. Należy pamiętać o tym, aby nie wyszukiwać elementów w tablicy
nieuporządkowanej, gdyż wynik jest nieprzewidywalny (nieskończona pętla
rekurencyjna, lub błędny wynik).
Jeżeli do sortowania tablicy stosujemy Comparator, to trzeba włączyć ten
Comparator podczas szukania:
import java.util.*;
public class search {
...
MyComparator comp = new MyComparator();
Arrays.binarySearch(tablica, szukany_element, comp);
}
Kontenery (Containers)
Kontenery dzielą się na dwie grupy
Kolekcje:
W skład kontenerów wchodzą trzy komponenty i po kilka ich implementacji
tj. ArrayList, LinkedList, Vector, Stack, TreeSet, HashSet, LinkedHashSet, HashMap,
LinkedHashMap, WeakHashMap, TreeMap, IdentityHashMap, HashTable
Lista (List) – przechowuje elementy w określonej kolejności, może
przechowywać powtarzające się elementy
Zbiór (Set) – przechowuje elementy w dowolnej kolejności, nie może zawierać
elementów zduplikowanych
Odwzorowanie:
Odwzorowanie (Map) – grupa par obiektów typu klucz-wartość
Kontenery diagram I
Diagram kontenerów:
Kontenery diagram II
Diagram kontenerów II:
Jak widać na diagramie, tak naprawdę są tam tylko trzy komponenty: Map,
List i Set. Reszta to interfejsy, klasy abstrakcyjne i klasy pochodne.
Kontenery diagram III
Diagram kontenerów III:
Jeszcze jeden diagram, tym razem w większym uproszczeniu
Kontenery - wypełnianie
Wypełnianie kontenerów:
Dla kontenerów istnieje klasa towarzysząca Collections zawierająca
statyczne metody usługowe, między nimi metodę fill(). Metoda ta zastępuje
elementy, które już są na liście. Działa tylko dla klasy List, a nie działa dla
Map i Set.
fill() ma tę samą wadę, co dla tablic – wypełnia całą listę referencją do
jednego obiektu. Przez to fill() jest niezbyt użyteczne.
Ponadto elementy można dodawać do kontenerów za pomocą metod
add() i put(). Będą one omówione dalej, przy okazji omówienia interfejsu
Collection.
Kontenery – wypisywanie I
Wypisywanie zawartości kontenerów:
Zawartość kontenerów można wypisać funkcją println() pobierającą jako
parametr referencję do kontenera. Przy odwołaniu, gdzie oczekiwany jest typ
String domyślnie wywoływana jest metoda toString(). Metodę toString można
przeciążać dla wszystkich typów obiektów.
Przykład:
import java.util.*;
public class ListPrint {
List list = new ArrayList();
for(int i=0; i<10; i++)
list.add(”element listy”);
System.out.println(list); // Wywoła domyślnie toString()
}
Kontenery – wypisywanie II
Wypisywanie adresu elementów kontenerów:
Jeżeli do wypisania adresu obiektu w pamięci użyty zostanie this, spowoduje to
powstanie rekursji i wygenerowana zostanie niekończąca się lista wyjątków. Aby
tego uniknąć należy zastosować super zamiast this.
Przykład 1: Przykład 2:
import java.util.*;
public class APrint1 {
public class MyObject {
public String toString() {
return ”adress:” + this;
}}
public static void main(String[] args) {
List list = new ArrayList();
for(int c; c<10; c++)
v.add(new MyObject);
System.out.println(list); }
import java.util.*;
public class APrint1 {
public class MyObject {
public String toString() {
return ”adress” + super;
}}
public static void main(String[] args) {
List list = new ArrayList();
for(int c; c<10; c++)
v.add(new MyObject);
System.out.println(list);}
zamiast
Kontenery – wypisywanie III
Sposób wyświetlania:
Zawartość kontenerów, wypisana za pomocą polecenia println, jest
wyświetlana w różny sposób, za zależności, jaki kontener jest wypisywany
List:
Zawartość kontenera jest wyświetlana w nawiasach kwadratowych:
”[element1, element2, element1, element3]”
• Set:
• Zawartość kontenera jest wyświetlana jak przy List, z tą różnicą, że elementy się
nie powtórzą (wynika to z właściwości zbioru Set):
”[element1, element2, element3]”
• Map:
• Zawartość kontenera jest wyświetlana w nawiasach klamrowych, w parach
klucz = wartość_skojarzona :
”{klucz1=element1, klucz2=element2, klucz3=element3}”
Wady kontenerów
Nieznany typ:
Konsekwencje:
Brak ograniczeń co do typu zamieszczanych obiektów.
Trzeba wykonywać rzutowanie do właściwego typu przed użyciem
obiektu
Wadą kontenerów jest brak informacji o przechowywanym typie. Kontener
przechowuje odwołania do obiektów klasy Object, będącej klasą bazową
wszystkich innych.
Klasa ArrayList o określonym typie:
public class MyList {
private List list = new ArrayList(); // przesłanianie funkcji, a nie przeciążanie
public void add(MyObj o) { list.add(o); } // jak w przypadku dziedziczenia
public MyObj get(int index) { return (MyObj)list.get(index); }
public int size() { return list.size(); }
}
Wymusza typ
Iteratory
Iterator jest obiektem służącym do przemieszczania się po ciągu elementów i
wybieranie napotkanych obiektów.
Iterator może być pobrany dzięki metodzie iterator(), zwracającej iterator do
danego kontenera.
Funkcje iteratora:
next() – uzyskuje następny obiekt z ciągu, a zwraca poprzedni obiekt
hasNext() – sprawdza, czy są następne obiekty
remove() – usuwa ostatni zwrócony przez iterator obiekt
Interfejs Collection – funkcje
Funkcje interfejsu Collection:
boolean add(Object) – dodaje argument (zwraca false, jeśli go nie doda)
boolean addAll(Collection) – umieszcza wszystkie elementy kontenera
argumenu (zwraca true, Jeżeli został dodany jakiś obiekt)
void clear() – usuwa wszystkie elementy
boolean contains(Objest) – sprawdza, czy obiekt jest w kontenerze
boolean containsAll(Collection) – sprawdza, czy kontener zawiera wszystkie
obiekty argumentu
boolean isEmpty() – sprawdza, czy kontener jest pusty
Iterator iterator() – zwraca iterator
boolean remove(Object) – usuwa obiekt
boolean removeAll(Collection) – usuwa wszystkie obiekty zawarte w
argumencie
Interfejs Collection – funkcji ciąg dalszy
Funkcje interfejsu Collection ciąg dalszy:
boolean retainAll(Object) – pozostawia przecięcie, z teorii zbiorów, dwóch
kontenerów
int size() – Zwraca liczbę elementów zawartych w kontenerze
Object[] toArray() – zamienia kontener na tablicę
Object[] toArray(Object[] a) – jak wyżej, typ tablicy przyjmuje typ argumentu
Interfejs Collection nie posiada funkcji get() pozwalającej na swobodny dostęp do
elementów. Do kategorii Collection należą też zbiory Set, toteż swobodny dostęp do
nich byłby bezsensowny.
W celu wydobycia elementu należy użyć iteratora.
Interfejs List
Funkcje interfejsu List:
add(Object) – wstawia element (addFirst(), addLast() dla LinkedList)
get() – pobiera element (getFirst(), getLast() dla LinkedList)
remove() – usuwa element (removeFirst(), removeLast() dla LinkedList)
iterator() – zwraca iterator
MyClass[] toArray(MyClass[] a) – jak wyżej, typ tablicy przyjmuje typ
argumentu
ArrayList:
Implementacja interfejsu List jako tablicy. Umożliwia szybszy dostęp do jej
elementów, kosztem wolniejszego ich wstawiania i usuwania.
LinkedList:
Zapewnia optymalny dostęp sekwencyjny, usuwanie i wstawianie elementów do
środka listy. Powolna w przypadku swobodnego dostępu. LinkedList pozwala na
zaimplementowanie stosu oraz kolejki.
Stos
Główne metody stosu:
public class MyStack {
private LinkedList list = new LinkedList;
public void push(Object o) { list.addFirst(o); }
public Object top() { return list.getFirst(); }
public Object pop() { return list.removeFirst|(); }
...
}
Stos jest przedstawiany jako kontener typu last-in, first-out (LIFO).
Można go stworzyć na podstawie LinkedList:
Kolejka
Główne metody kolejki:
public class MyQueue {
private LinkedList list = new LinkedList;
public void put(Object o) { list.addFirst(o); }
public Object get() { return list.removeLast(); }
...
}
Kolejkę jest kontenerem typu first-in, first-out (FIFO). Można ją
stworzyć na podstawie LinkedList:
Interfejs Set
Set ma ten sam interfejs co Collection, pomimo to zachowuje się inaczej. Set nie
pozwala na przechowywanie więcej niż jednego egzemplarza wartości każdego z
obiektów. Interfejs ten nie zapewnia utrzymania elementów w żadnym porządku.
Elementy Object zamieszczane w Set muszą definiować metodę equals(), w celu
ustalenia, czy element już przypadkiem nie należy do zbioru.
HashSet:
Zapewnia krótki czas lokalizacji elementu. Wymaga zdefiniowania metody
hashCode() i equals() dla elementów klasy Object.
TreeSet:
Zbiór uporządkowany na podstawie drzewa. Dzięki niemu można pobierać
uporządkowany ciąg elementów. Jako zbiór uporządkowany, dostarcza metody
first() i last() zwracające najmniejszy i największy element.
Interfejs Set - ciąg dalszy
LinkedHashSet:
Cechuje się taką samą szybkością dostępu jak hashSet, z tym, że LinkedHashSet
zachowuje oryginalną kolejność dodawania elementów. Klasa ta oparta jest na
LinkedList. Wyniki pobierane są w kolejności jakiej były dodawane do kontenera.
Każdy typów zbioru Set przechowuje elementy w innej kolejności:
W zbiorach HashSet, TreeSet i LinkedHashSet zamieszczone zostały elementy
od 0 do 9. Po wypisaniu tych zbiorów otrzymamy:
[ 2 , 4 , 9 , 8 , 6 , 1 , 3 , 7 , 5 , 0 ] – HashSet
[ 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 ] – TreeSet
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] – LinkedHashSet
Przy tworzeniu własnego typu, należy pamiętać o zaimplementowaniu interfejsu
Compareble i metody compareTo(), aby zbiór Set mógł określić przynależność jakiegoś
elementu do zbioru, oraz aby mógł zbiór posortować (w przypadku TreeSet).
SortedSet
Jedyną dostępną implementacją SortedSet jest klasa TreeSet. W związku z
powyższym, TreeSet dostarcza metody interfejsu SortedSet:
Comparator comparator(); /* Zwraca interfejs Comparator, lub null dla
domyślnego comparatora */
Object first() ; // Podaje najmniejszy element
Object last(); // Podaje największy element
SortedSet subSet(odElementu, doElementu); /* zwraca fragment zbioru
obejmujący element odElementu, do elementu doElementu */
SortedSet headSet(doElementu); /* Zwraca fragment zbioru o elementach
mniejszych od elementu doElementu */
SortedSet tailSet(odElementu); /* Zwraca fragment zbioru o elementach
większych od elementu odElementu */
Interfejs Map
Kontener Map pozwala na przechowywanie par elementów {klucz, obiekt}.
Interfejs Map dostarcza kilka podstawowych metod do obsługi tego rodzaju
kontenerów:
put(Object klucz, Object wartość); // dodaje wartość i wiąże ją z kluczem
get(Object klucz); // zwraca wartość związaną z kluczem
containsKey(); // sprawdza czy odwzorowanie zawiera klucz
containsValue(); // sprawdza czy odwzorowanie zawiera wartość;
Odwzorowania Map do wyszukiwania elementów wykorzystują metodę
hashCode() obiektu przechowywanego, przez co wyszukiwanie elementów w
odwzorowaniach Map jest szybsze, niż gdyby zastosowana została metoda z
ArrayList.
Interfejs Map - typy
W Javie dostępne są kilka typów odwzorowań Map: HashMap, TreeMap,
LinkedhashMap, WeakhashMap oraz IdentityHashMap.
HashMap:
implementacja oparta na tablicy asocjacyjnej (dawniej HashTable).
Zapewnia lokalizację i wstawianie par w czasie stałym. Zachowanie może
być regulowane, poprzez ustawienie parametrów w konstruktorze.
LinkedHashMap (JDK 1.4):
Implementacja podobna do HashMap, lecz podczas przeglądania pary
zwracane są w kolejności wstawiania. Działa trochę wolniej od HashMap.
Wstawianie elementów odbywa się szybciej niż w HashMap, dzięki
wykorzystaniu listy połączonej.
Interfejs Map – typy – ciąg dalszy
WeakHashMap:
Odwzorowanie operujące na słabych klucza, umożliwiające usunięcie z pamięci
obiektów przechowywanych w mapie. Obiekty bez odwołań są usuwane przez
odśmiecacz pamięci, jeżeli w programie nie ma odwołań do nich.
IdentityHashMap (JDK 1.4):
Odwzorowanie hash’ujące określające równość kluczy przy wykorzystaniu
operatora == zamiast metody equals(). Nie nadaje się do zastosowań ogólnych.
TreeSet:
Implementacja oparta na drzewach czerwono-czarnych. Pary po umieszczeniu są
sortowane według porządku wyznaczonym przez interfejs Comparable. Dzieki
posortowaniu dostępne są funkcje firstKey() i lastKey() zwracające najmniejszy i
największy z kluczy. Ponadto posiada metodę subMap() pozwalającą uzyskać
fragment drzewa.
Interfejs Map – wydajność
Czynniki wydajności HashMap:
pojemność – liczba komórek tablicy
pojemność początkowa – liczba komórek tablicy po jej stworzeniu
rozmiar – liczba pozycji znajdujących się obecnie w tablicy
współczynnik zapełnienia – 0 oznacza pustą tablicę, 1 pełną
(rozmiar/pojemność). Współczynnik ten oznacza, że gdy kontener osiągnie
ten współczynnik zapełnienia, to automatycznie zwiększy swój rozmiar
(pojemność).
Powyższe współczynniki można ustawiać w konstruktorze, w celu zmiany
zachowania odwzorowania.
SortedMap
Jedyną dostępną implementacją SortedMap jest klasa TreeMap. W związku z
powyższym, TreeMap dostarcza metody interfejsu SortedMap:
Comparator comparator(); /* Zwraca interfejs Comparator, lub null w
przypadku domyślnego comparatora*/
Object firstKey() ; // Podaje najmniejszy klucz
Object lastKey(); // Podaje największy klucz
SortedMap subMap(odKlucza, doklucza); /* zwraca fragment
odwzorowania obejmujący klucze odKlucza, do klucza doKlucza */
SortedMap headMap(doKlucza); /* Zwraca fragment odwzorowania o
kluczach mniejszych od klucza doKlucza */
SortedMap tailMap(odKlucza); /* Zwraca fragment odwzorowania o
kluczach większych od klucza odKlucza */
Hash’owanie
Każdy obiekt domyślnie dziedziczy po klasie Object, jeżeli nie określimy
klasy bazowej. Dlatego też każdy obiekt dziedziczy metodę hashCode() klasy
Object, zwracającą adres danego obiektu.
Aby można było korzystać z odwzorowań dla własnych obiektów, należy
zaimplementować metodę equals() i interfejs Comparable, a także metodę
hashCode() (chyba, że chcemy skorzystać z domyślnej metody klasy Object).
Powinno się za każdym razem przesłaniać metodę hashCode(), w celu
uzyskania poprawnego działania kontenerów opartych na działaniu tej metody.
Przykład:
public putInHash() {
Map map = new HashMap();
int[] tab1 = {1, 2};
int[] tab2 = {2, 1};
map.put(tab1, new Object());
map.put(tab2, new Object()); }
Różne dla equals()
Takie samo dla hashCode()
Hash’owanie przykład kolejny
public putInHash() {
public class MyKey {
int field1;
int field2;
MyKey(int f1, int f2) {
field1 = f1;
field2 = f2;}
Map map = new HashMap();
MyKey k1 = new MyKey(1, 2);
MyKey k2 = new MyKey(2, 1);
map.put(k1, new Object());
map.put(k2, new Object()); }
różne dla equals()
Takie samo dla hashCode()
Jak poprzednio:
hashCode() - przykład
public class TShirt {
int ID; //number on tshirt
int size; // size of tshirt
String desc; // description
public TShirt(int id, int s, String d) {
ID = id;
size = s;
description = d; }
public int hashCode() { return ID; };
public boolean equals(Object o) {
return ( desc.equals(((TShirt) o).desc)
&& size == ((TShirt) o).size); }
}
public class PList{
String[] players = {”p1”, ”p2”};
TShirt ts1 = new TShirt(1, 7,
”player’s 1 shirt”);
TShirt ts2 = new TShirt(2, 7,
”player’s 1 shirt”);
public Plist() {
Map map = new HashMap();
map.put(ts1, players[0]);
map.put(ts2, players[1]);
};
}
Dodatkowe usługi klasy Collections
max(Collection) – zwraca maksymalny element, stosuje normalną metodę
porównania dla obiektów w strukturze
min(Collection) – jak wyżej, lecz zwraca element minimalny
max(Collection,Comparator) – j.w. stosuje Comparator do porównań
min(Collection,Comparator) – analogicznie do powyższego
indexOfSublist(List Source, List Target) – podaje indeks pierwszego miejsca, w
którym Target występuje w Source
lastOfSublist(List Source, List Target) – analogicznie do powyższego
replaceAll(List list, Object old, Object new) – zamienia old na new w liscie
reverse(List) – odwracanie kolejności występowania
rotate(List list, int dist) – przesuwa elementy listy o dystans dist
copy(List target, List source) – kopiuje elementy z source do taget
swap(List list, int i, int j) – zamienia położenie elementów i i j
Dodatkowe usługi klasy Collections
nCopies(int n, Object o) – zwraca niemodyfikowalną listę rozmiaru n, której
wszystkie odwołania wskazują na obiekt o
Enumeration(Collection) – zwraca obiekt Enumeration dla podanego argumentu
List(Enumeration e) – Zwraca obiekt ArrayList wygenerowany przy użyciu podanego
obiektu Enumeration
Synchronizacja
Kolekcje mogą być obsługiwane przez kilka wątków jednocześnie, co z kolei
wymaga, aby ta kolekcja była w jakiś sposób synchronizowana.
Klasa Collections udostępnia sposób automatycznej synchronizacji całego kontenera.
Dzięki temu nie ma sposobności przypadkowego udostępnienia wersji
niezsynchronizowanej.
Przykłady:
Set s = Collections.synchronizedSet(new HashSet());
List l = Collections.synchronizedList(new ArrayList());
Map m = Collections.synchronizedMap(new HashMap());
W powyższych przypadkach, nowy kontener jest natychmiast przekazywany do
odpowiedniej metody synchronizującej.
fail-fast
Kontenery w Javie posiadają również zabezpieczenie przed modyfikacją ich
zawartości przez więcej niż jeden proces.
Mechanizm fail-fast wyszukuje wszystkie zmiany kontenera, nie pochodzące od
danego procesu. Jeżeli inny proces modyfikuje kontener, spowoduje to pojawienie się
wyjątku ConcurrentModificationException.
Przykład:
public class MyClass {
public static void main(String[] args) {
Collection c = new ArrayList();
Iterator it = c.interator();
c.add(”My object”);
}
Spowoduje pojawienie się wyjątku
ConcurrentModificationException
Koniec
„To już jest koniec.
Nie ma już nic.
Jesteśmy wolni. (?)
Możemy iść.” (???)

More Related Content

PDF
2024 Trend Updates: What Really Works In SEO & Content Marketing
PDF
Storytelling For The Web: Integrate Storytelling in your Design Process
PDF
Artificial Intelligence, Data and Competition – SCHREPEL – June 2024 OECD dis...
PDF
How to Leverage AI to Boost Employee Wellness - Lydia Di Francesco - SocialHR...
PDF
2024 State of Marketing Report – by Hubspot
PDF
Everything You Need To Know About ChatGPT
PDF
Product Design Trends in 2024 | Teenage Engineerings
PDF
How Race, Age and Gender Shape Attitudes Towards Mental Health
2024 Trend Updates: What Really Works In SEO & Content Marketing
Storytelling For The Web: Integrate Storytelling in your Design Process
Artificial Intelligence, Data and Competition – SCHREPEL – June 2024 OECD dis...
How to Leverage AI to Boost Employee Wellness - Lydia Di Francesco - SocialHR...
2024 State of Marketing Report – by Hubspot
Everything You Need To Know About ChatGPT
Product Design Trends in 2024 | Teenage Engineerings
How Race, Age and Gender Shape Attitudes Towards Mental Health
Ad

3b.ppt

  • 1. „Klasy kolekcji” by Szymon Natanek Techniki i języki programowania
  • 2. Kolekcje obiektów Tablice: Klasa Arrays Listy: Klasy: ArrayList, LinkedList, Vector, Stack Zbiory: Klasy: TreeSet, HashSet, LinkedHashSet Mapy: Klasy: HashMap, LinkedHashMap, WeakHashMap, TreeMap, IdentityHashMap, HashTable
  • 3. Tablice Tablica w Javie jest ciągiem obiektów (a raczej referencji do obiektów). Jest prostą sekwencją liniową, pozwalającą na szybki dostęp do elementów. Tablice posiadają ograniczenie rozmiaru. Można stworzyć tablicę o określonym rozmiarze, ale nie można tego rozmiaru zmienić w czasie życia obiektu. Aby uniknąć powyższego problemu, po zapełnieniu tablicy, można utworzyć nową, o większym rozmiarze i skopiować zawartość poprzedniej do niej. Jest to jednakże rozwiązanie mało wygodne. Inicjowanie tablic: MyObj[] tablica1; // niezainicjowana MyObj tablica2[]; // jak wyżej MyObj[] tablica3 = new MyObj[5] // zainicjowana tablica 5 elementowa MyObj[] tablica4 ={new MyObj(), new MyObj()} /* zainicjowana tablica 2 elementowa */ Zaletą tablicy jest sprawdzanie zakresu, dlatego też nie możliwe jest wyjście poza ściśle określony zakres tablicy, jak miało to miejsce w C lub C++
  • 4. Tablice - ciąg dalszy Tablicy niezainicjowanej nie można użyć. Jeżeli nastąpi odwołanie do niezainicjowanej tablicy, kompilator powiadomi nas o błędzie: int[] tablica1; // Tablica nie zainicjowana int[] tablilca2 = new int[6]; int length = tablica1.length + tablica2.length; // Błąd Tablica posiada składową tylko do odczytu length, będącą długością tablicy (lecz nie obecnej ilości obiektów, tylko ilość obiektów jakie można umieścić). W przeciwieństwie do klas kontenerowych, które przechowują referencje do obiektów, tablice mogą przechowywać również zmienne typu podstawowego. Tablice mogą być zwracane przez funkcje. W C lub C++ można było zwrócić wskaźnik do tablicy jako wynik funkcji. Java natomiast daje możliwość zwracania całej tablicy. Zwolnieniem pamięci z takiej tablicy nie trzeba się martwić, ponieważ po zniknięciu wszystkich odwołań do niej, zostanie automatycznie usunięta.
  • 5. Arrays Klasa Arrays, zamieszczona w pakiecie java.util, posiada zestaw metod statycznych do wykonywania operacji na tablicach: equals(tablica1, tablica2); // Porównuje tablice pod względem równości fill(tablica, wartość); // Wypełnia tablicę określoną wartością sort(tablica, comparator); // Sortuje tablicę (comparator opcjonalny) binarySearch(tablica, szukany_element, comparator ); /*Wyszukuje element w tablicy posortowanej*/ Powyższe metody są przeciążone dla każdego z podstawowych typów i klasy Object Ponadto klasa Arrays posiada metodę toList(), która zmienia tablicę w kontener List Do kopiowania tablicy służy statyczna metoda System.arraycopy(), pozwalająca na kopiowanie tablic, szybciej niż za pomocą własnej pętli for. Jest ona przeciążona tak, że obsługuje tablice wszystkich typów. Do porównywania elementów tablicy służy metoda compareTo() zamieszczona w interfejsie java.lang.Comparable
  • 6. Porównywanie elementów Do porównywania elementów tablicy służy metoda compareTo() zamieszczona w interfejsie java.lang.Comparable. Metoda ta jest niezbędna do działania metody sort() Przykład 1 – porównanie z wykorzystaniem compareTo(): import java.util.*; public class CompType implements Comparable { int value; ... public int compareTo(Object obj) { int val = ((CompType)obj).value; return (value < val ? –1 : (value == val ? 0 : 1)); }
  • 7. Porównywanie elementów – ciąg dalszy Przykład 2 – porównanie przy użyciu compare(): import java.util.*; public class MyComp implements Comparable { public int compare(Object o1, Object o2) { int val = ((CompType)o1).value; int val2 = ((CompType)o2).value; return (val1 < val2 ? –1 : (val1 == val2 ? 0 : 1)); }
  • 8. Wyszukiwanie elementów Metody porównań wykorzystywane są również do wyszukiwania elementów za pomocą binarySearch(). Przeszukiwanie tablic, jak sama nazwa sugeruje, odbywa się stosując wyszukiwanie binarne. Należy pamiętać o tym, aby nie wyszukiwać elementów w tablicy nieuporządkowanej, gdyż wynik jest nieprzewidywalny (nieskończona pętla rekurencyjna, lub błędny wynik). Jeżeli do sortowania tablicy stosujemy Comparator, to trzeba włączyć ten Comparator podczas szukania: import java.util.*; public class search { ... MyComparator comp = new MyComparator(); Arrays.binarySearch(tablica, szukany_element, comp); }
  • 9. Kontenery (Containers) Kontenery dzielą się na dwie grupy Kolekcje: W skład kontenerów wchodzą trzy komponenty i po kilka ich implementacji tj. ArrayList, LinkedList, Vector, Stack, TreeSet, HashSet, LinkedHashSet, HashMap, LinkedHashMap, WeakHashMap, TreeMap, IdentityHashMap, HashTable Lista (List) – przechowuje elementy w określonej kolejności, może przechowywać powtarzające się elementy Zbiór (Set) – przechowuje elementy w dowolnej kolejności, nie może zawierać elementów zduplikowanych Odwzorowanie: Odwzorowanie (Map) – grupa par obiektów typu klucz-wartość
  • 11. Kontenery diagram II Diagram kontenerów II: Jak widać na diagramie, tak naprawdę są tam tylko trzy komponenty: Map, List i Set. Reszta to interfejsy, klasy abstrakcyjne i klasy pochodne.
  • 12. Kontenery diagram III Diagram kontenerów III: Jeszcze jeden diagram, tym razem w większym uproszczeniu
  • 13. Kontenery - wypełnianie Wypełnianie kontenerów: Dla kontenerów istnieje klasa towarzysząca Collections zawierająca statyczne metody usługowe, między nimi metodę fill(). Metoda ta zastępuje elementy, które już są na liście. Działa tylko dla klasy List, a nie działa dla Map i Set. fill() ma tę samą wadę, co dla tablic – wypełnia całą listę referencją do jednego obiektu. Przez to fill() jest niezbyt użyteczne. Ponadto elementy można dodawać do kontenerów za pomocą metod add() i put(). Będą one omówione dalej, przy okazji omówienia interfejsu Collection.
  • 14. Kontenery – wypisywanie I Wypisywanie zawartości kontenerów: Zawartość kontenerów można wypisać funkcją println() pobierającą jako parametr referencję do kontenera. Przy odwołaniu, gdzie oczekiwany jest typ String domyślnie wywoływana jest metoda toString(). Metodę toString można przeciążać dla wszystkich typów obiektów. Przykład: import java.util.*; public class ListPrint { List list = new ArrayList(); for(int i=0; i<10; i++) list.add(”element listy”); System.out.println(list); // Wywoła domyślnie toString() }
  • 15. Kontenery – wypisywanie II Wypisywanie adresu elementów kontenerów: Jeżeli do wypisania adresu obiektu w pamięci użyty zostanie this, spowoduje to powstanie rekursji i wygenerowana zostanie niekończąca się lista wyjątków. Aby tego uniknąć należy zastosować super zamiast this. Przykład 1: Przykład 2: import java.util.*; public class APrint1 { public class MyObject { public String toString() { return ”adress:” + this; }} public static void main(String[] args) { List list = new ArrayList(); for(int c; c<10; c++) v.add(new MyObject); System.out.println(list); } import java.util.*; public class APrint1 { public class MyObject { public String toString() { return ”adress” + super; }} public static void main(String[] args) { List list = new ArrayList(); for(int c; c<10; c++) v.add(new MyObject); System.out.println(list);} zamiast
  • 16. Kontenery – wypisywanie III Sposób wyświetlania: Zawartość kontenerów, wypisana za pomocą polecenia println, jest wyświetlana w różny sposób, za zależności, jaki kontener jest wypisywany List: Zawartość kontenera jest wyświetlana w nawiasach kwadratowych: ”[element1, element2, element1, element3]” • Set: • Zawartość kontenera jest wyświetlana jak przy List, z tą różnicą, że elementy się nie powtórzą (wynika to z właściwości zbioru Set): ”[element1, element2, element3]” • Map: • Zawartość kontenera jest wyświetlana w nawiasach klamrowych, w parach klucz = wartość_skojarzona : ”{klucz1=element1, klucz2=element2, klucz3=element3}”
  • 17. Wady kontenerów Nieznany typ: Konsekwencje: Brak ograniczeń co do typu zamieszczanych obiektów. Trzeba wykonywać rzutowanie do właściwego typu przed użyciem obiektu Wadą kontenerów jest brak informacji o przechowywanym typie. Kontener przechowuje odwołania do obiektów klasy Object, będącej klasą bazową wszystkich innych. Klasa ArrayList o określonym typie: public class MyList { private List list = new ArrayList(); // przesłanianie funkcji, a nie przeciążanie public void add(MyObj o) { list.add(o); } // jak w przypadku dziedziczenia public MyObj get(int index) { return (MyObj)list.get(index); } public int size() { return list.size(); } } Wymusza typ
  • 18. Iteratory Iterator jest obiektem służącym do przemieszczania się po ciągu elementów i wybieranie napotkanych obiektów. Iterator może być pobrany dzięki metodzie iterator(), zwracającej iterator do danego kontenera. Funkcje iteratora: next() – uzyskuje następny obiekt z ciągu, a zwraca poprzedni obiekt hasNext() – sprawdza, czy są następne obiekty remove() – usuwa ostatni zwrócony przez iterator obiekt
  • 19. Interfejs Collection – funkcje Funkcje interfejsu Collection: boolean add(Object) – dodaje argument (zwraca false, jeśli go nie doda) boolean addAll(Collection) – umieszcza wszystkie elementy kontenera argumenu (zwraca true, Jeżeli został dodany jakiś obiekt) void clear() – usuwa wszystkie elementy boolean contains(Objest) – sprawdza, czy obiekt jest w kontenerze boolean containsAll(Collection) – sprawdza, czy kontener zawiera wszystkie obiekty argumentu boolean isEmpty() – sprawdza, czy kontener jest pusty Iterator iterator() – zwraca iterator boolean remove(Object) – usuwa obiekt boolean removeAll(Collection) – usuwa wszystkie obiekty zawarte w argumencie
  • 20. Interfejs Collection – funkcji ciąg dalszy Funkcje interfejsu Collection ciąg dalszy: boolean retainAll(Object) – pozostawia przecięcie, z teorii zbiorów, dwóch kontenerów int size() – Zwraca liczbę elementów zawartych w kontenerze Object[] toArray() – zamienia kontener na tablicę Object[] toArray(Object[] a) – jak wyżej, typ tablicy przyjmuje typ argumentu Interfejs Collection nie posiada funkcji get() pozwalającej na swobodny dostęp do elementów. Do kategorii Collection należą też zbiory Set, toteż swobodny dostęp do nich byłby bezsensowny. W celu wydobycia elementu należy użyć iteratora.
  • 21. Interfejs List Funkcje interfejsu List: add(Object) – wstawia element (addFirst(), addLast() dla LinkedList) get() – pobiera element (getFirst(), getLast() dla LinkedList) remove() – usuwa element (removeFirst(), removeLast() dla LinkedList) iterator() – zwraca iterator MyClass[] toArray(MyClass[] a) – jak wyżej, typ tablicy przyjmuje typ argumentu ArrayList: Implementacja interfejsu List jako tablicy. Umożliwia szybszy dostęp do jej elementów, kosztem wolniejszego ich wstawiania i usuwania. LinkedList: Zapewnia optymalny dostęp sekwencyjny, usuwanie i wstawianie elementów do środka listy. Powolna w przypadku swobodnego dostępu. LinkedList pozwala na zaimplementowanie stosu oraz kolejki.
  • 22. Stos Główne metody stosu: public class MyStack { private LinkedList list = new LinkedList; public void push(Object o) { list.addFirst(o); } public Object top() { return list.getFirst(); } public Object pop() { return list.removeFirst|(); } ... } Stos jest przedstawiany jako kontener typu last-in, first-out (LIFO). Można go stworzyć na podstawie LinkedList:
  • 23. Kolejka Główne metody kolejki: public class MyQueue { private LinkedList list = new LinkedList; public void put(Object o) { list.addFirst(o); } public Object get() { return list.removeLast(); } ... } Kolejkę jest kontenerem typu first-in, first-out (FIFO). Można ją stworzyć na podstawie LinkedList:
  • 24. Interfejs Set Set ma ten sam interfejs co Collection, pomimo to zachowuje się inaczej. Set nie pozwala na przechowywanie więcej niż jednego egzemplarza wartości każdego z obiektów. Interfejs ten nie zapewnia utrzymania elementów w żadnym porządku. Elementy Object zamieszczane w Set muszą definiować metodę equals(), w celu ustalenia, czy element już przypadkiem nie należy do zbioru. HashSet: Zapewnia krótki czas lokalizacji elementu. Wymaga zdefiniowania metody hashCode() i equals() dla elementów klasy Object. TreeSet: Zbiór uporządkowany na podstawie drzewa. Dzięki niemu można pobierać uporządkowany ciąg elementów. Jako zbiór uporządkowany, dostarcza metody first() i last() zwracające najmniejszy i największy element.
  • 25. Interfejs Set - ciąg dalszy LinkedHashSet: Cechuje się taką samą szybkością dostępu jak hashSet, z tym, że LinkedHashSet zachowuje oryginalną kolejność dodawania elementów. Klasa ta oparta jest na LinkedList. Wyniki pobierane są w kolejności jakiej były dodawane do kontenera. Każdy typów zbioru Set przechowuje elementy w innej kolejności: W zbiorach HashSet, TreeSet i LinkedHashSet zamieszczone zostały elementy od 0 do 9. Po wypisaniu tych zbiorów otrzymamy: [ 2 , 4 , 9 , 8 , 6 , 1 , 3 , 7 , 5 , 0 ] – HashSet [ 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 ] – TreeSet [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] – LinkedHashSet Przy tworzeniu własnego typu, należy pamiętać o zaimplementowaniu interfejsu Compareble i metody compareTo(), aby zbiór Set mógł określić przynależność jakiegoś elementu do zbioru, oraz aby mógł zbiór posortować (w przypadku TreeSet).
  • 26. SortedSet Jedyną dostępną implementacją SortedSet jest klasa TreeSet. W związku z powyższym, TreeSet dostarcza metody interfejsu SortedSet: Comparator comparator(); /* Zwraca interfejs Comparator, lub null dla domyślnego comparatora */ Object first() ; // Podaje najmniejszy element Object last(); // Podaje największy element SortedSet subSet(odElementu, doElementu); /* zwraca fragment zbioru obejmujący element odElementu, do elementu doElementu */ SortedSet headSet(doElementu); /* Zwraca fragment zbioru o elementach mniejszych od elementu doElementu */ SortedSet tailSet(odElementu); /* Zwraca fragment zbioru o elementach większych od elementu odElementu */
  • 27. Interfejs Map Kontener Map pozwala na przechowywanie par elementów {klucz, obiekt}. Interfejs Map dostarcza kilka podstawowych metod do obsługi tego rodzaju kontenerów: put(Object klucz, Object wartość); // dodaje wartość i wiąże ją z kluczem get(Object klucz); // zwraca wartość związaną z kluczem containsKey(); // sprawdza czy odwzorowanie zawiera klucz containsValue(); // sprawdza czy odwzorowanie zawiera wartość; Odwzorowania Map do wyszukiwania elementów wykorzystują metodę hashCode() obiektu przechowywanego, przez co wyszukiwanie elementów w odwzorowaniach Map jest szybsze, niż gdyby zastosowana została metoda z ArrayList.
  • 28. Interfejs Map - typy W Javie dostępne są kilka typów odwzorowań Map: HashMap, TreeMap, LinkedhashMap, WeakhashMap oraz IdentityHashMap. HashMap: implementacja oparta na tablicy asocjacyjnej (dawniej HashTable). Zapewnia lokalizację i wstawianie par w czasie stałym. Zachowanie może być regulowane, poprzez ustawienie parametrów w konstruktorze. LinkedHashMap (JDK 1.4): Implementacja podobna do HashMap, lecz podczas przeglądania pary zwracane są w kolejności wstawiania. Działa trochę wolniej od HashMap. Wstawianie elementów odbywa się szybciej niż w HashMap, dzięki wykorzystaniu listy połączonej.
  • 29. Interfejs Map – typy – ciąg dalszy WeakHashMap: Odwzorowanie operujące na słabych klucza, umożliwiające usunięcie z pamięci obiektów przechowywanych w mapie. Obiekty bez odwołań są usuwane przez odśmiecacz pamięci, jeżeli w programie nie ma odwołań do nich. IdentityHashMap (JDK 1.4): Odwzorowanie hash’ujące określające równość kluczy przy wykorzystaniu operatora == zamiast metody equals(). Nie nadaje się do zastosowań ogólnych. TreeSet: Implementacja oparta na drzewach czerwono-czarnych. Pary po umieszczeniu są sortowane według porządku wyznaczonym przez interfejs Comparable. Dzieki posortowaniu dostępne są funkcje firstKey() i lastKey() zwracające najmniejszy i największy z kluczy. Ponadto posiada metodę subMap() pozwalającą uzyskać fragment drzewa.
  • 30. Interfejs Map – wydajność Czynniki wydajności HashMap: pojemność – liczba komórek tablicy pojemność początkowa – liczba komórek tablicy po jej stworzeniu rozmiar – liczba pozycji znajdujących się obecnie w tablicy współczynnik zapełnienia – 0 oznacza pustą tablicę, 1 pełną (rozmiar/pojemność). Współczynnik ten oznacza, że gdy kontener osiągnie ten współczynnik zapełnienia, to automatycznie zwiększy swój rozmiar (pojemność). Powyższe współczynniki można ustawiać w konstruktorze, w celu zmiany zachowania odwzorowania.
  • 31. SortedMap Jedyną dostępną implementacją SortedMap jest klasa TreeMap. W związku z powyższym, TreeMap dostarcza metody interfejsu SortedMap: Comparator comparator(); /* Zwraca interfejs Comparator, lub null w przypadku domyślnego comparatora*/ Object firstKey() ; // Podaje najmniejszy klucz Object lastKey(); // Podaje największy klucz SortedMap subMap(odKlucza, doklucza); /* zwraca fragment odwzorowania obejmujący klucze odKlucza, do klucza doKlucza */ SortedMap headMap(doKlucza); /* Zwraca fragment odwzorowania o kluczach mniejszych od klucza doKlucza */ SortedMap tailMap(odKlucza); /* Zwraca fragment odwzorowania o kluczach większych od klucza odKlucza */
  • 32. Hash’owanie Każdy obiekt domyślnie dziedziczy po klasie Object, jeżeli nie określimy klasy bazowej. Dlatego też każdy obiekt dziedziczy metodę hashCode() klasy Object, zwracającą adres danego obiektu. Aby można było korzystać z odwzorowań dla własnych obiektów, należy zaimplementować metodę equals() i interfejs Comparable, a także metodę hashCode() (chyba, że chcemy skorzystać z domyślnej metody klasy Object). Powinno się za każdym razem przesłaniać metodę hashCode(), w celu uzyskania poprawnego działania kontenerów opartych na działaniu tej metody. Przykład: public putInHash() { Map map = new HashMap(); int[] tab1 = {1, 2}; int[] tab2 = {2, 1}; map.put(tab1, new Object()); map.put(tab2, new Object()); } Różne dla equals() Takie samo dla hashCode()
  • 33. Hash’owanie przykład kolejny public putInHash() { public class MyKey { int field1; int field2; MyKey(int f1, int f2) { field1 = f1; field2 = f2;} Map map = new HashMap(); MyKey k1 = new MyKey(1, 2); MyKey k2 = new MyKey(2, 1); map.put(k1, new Object()); map.put(k2, new Object()); } różne dla equals() Takie samo dla hashCode() Jak poprzednio:
  • 34. hashCode() - przykład public class TShirt { int ID; //number on tshirt int size; // size of tshirt String desc; // description public TShirt(int id, int s, String d) { ID = id; size = s; description = d; } public int hashCode() { return ID; }; public boolean equals(Object o) { return ( desc.equals(((TShirt) o).desc) && size == ((TShirt) o).size); } } public class PList{ String[] players = {”p1”, ”p2”}; TShirt ts1 = new TShirt(1, 7, ”player’s 1 shirt”); TShirt ts2 = new TShirt(2, 7, ”player’s 1 shirt”); public Plist() { Map map = new HashMap(); map.put(ts1, players[0]); map.put(ts2, players[1]); }; }
  • 35. Dodatkowe usługi klasy Collections max(Collection) – zwraca maksymalny element, stosuje normalną metodę porównania dla obiektów w strukturze min(Collection) – jak wyżej, lecz zwraca element minimalny max(Collection,Comparator) – j.w. stosuje Comparator do porównań min(Collection,Comparator) – analogicznie do powyższego indexOfSublist(List Source, List Target) – podaje indeks pierwszego miejsca, w którym Target występuje w Source lastOfSublist(List Source, List Target) – analogicznie do powyższego replaceAll(List list, Object old, Object new) – zamienia old na new w liscie reverse(List) – odwracanie kolejności występowania rotate(List list, int dist) – przesuwa elementy listy o dystans dist copy(List target, List source) – kopiuje elementy z source do taget swap(List list, int i, int j) – zamienia położenie elementów i i j
  • 36. Dodatkowe usługi klasy Collections nCopies(int n, Object o) – zwraca niemodyfikowalną listę rozmiaru n, której wszystkie odwołania wskazują na obiekt o Enumeration(Collection) – zwraca obiekt Enumeration dla podanego argumentu List(Enumeration e) – Zwraca obiekt ArrayList wygenerowany przy użyciu podanego obiektu Enumeration
  • 37. Synchronizacja Kolekcje mogą być obsługiwane przez kilka wątków jednocześnie, co z kolei wymaga, aby ta kolekcja była w jakiś sposób synchronizowana. Klasa Collections udostępnia sposób automatycznej synchronizacji całego kontenera. Dzięki temu nie ma sposobności przypadkowego udostępnienia wersji niezsynchronizowanej. Przykłady: Set s = Collections.synchronizedSet(new HashSet()); List l = Collections.synchronizedList(new ArrayList()); Map m = Collections.synchronizedMap(new HashMap()); W powyższych przypadkach, nowy kontener jest natychmiast przekazywany do odpowiedniej metody synchronizującej.
  • 38. fail-fast Kontenery w Javie posiadają również zabezpieczenie przed modyfikacją ich zawartości przez więcej niż jeden proces. Mechanizm fail-fast wyszukuje wszystkie zmiany kontenera, nie pochodzące od danego procesu. Jeżeli inny proces modyfikuje kontener, spowoduje to pojawienie się wyjątku ConcurrentModificationException. Przykład: public class MyClass { public static void main(String[] args) { Collection c = new ArrayList(); Iterator it = c.interator(); c.add(”My object”); } Spowoduje pojawienie się wyjątku ConcurrentModificationException
  • 39. Koniec „To już jest koniec. Nie ma już nic. Jesteśmy wolni. (?) Możemy iść.” (???)