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.
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ść.” (???)