Kolekcje • • • • Cechy kolekcji Przegląd interfejsów i klas kolekcji Przykłady użycia Iteratory Cechy kolekcji Kolekcje w Javie przechowują grupy obiektów, wykorzystując gotowe algorytmy składowania danych • Siła kolekcji leży w tym, że są zamienne – mają taki sam interfejs, a różnią się implementacją • Są dwa podstawowe interfejsy: Collection i Map, inne interfejsy są ich rozszerzeniem • Platforma Java zawiera różne implementacje tych samych interfejsów • Każdą kolekcję obiektów można przejść z użyciem iteratora (uniwersalnie), lub w przewidziany tylko dla niej sposób (specyficznie) Interfejsy kolekcji • Kolekcja (Collection) reprezentuje grupę obiektów (elementów) • Zbiór (Set) jest kolekcją która nie może zawierać powtórzeń elementów • Lista (List) jest kolekcją z ustalonym porządkiem (kolejnością) elementów, może zawierać powtórzenia • Odwzorowanie (Map) przyporządkowuje do danych kluczy wartości, klucze nie mogą się powtarzać Cechy kolekcji (c.d.) • Kolekcje przechowują referencje do Object, nie są więc czułe na typ danych (gubią informację o typie) • Przy odczycie elementów trzeba wykonywać rzutowanie do odpowiedniego typu danych • W jednej kolekcji mogą być obiekty różnych klas (ponieważ wszystkie są zgodne z Object) • Używając typów opakowujących (ang. wrapper types) można umieszczać w kolekcjach m.in. liczby i pojedyncze litery • Używając metody size() można odczytać aktualną ilość elementów w kolekcji • Kolekcje znajdują się w pakiecie java.util Użycie kolekcji List zakupy = new ArrayList(); zakupy.add("Mleko"); zakupy.add("Rogalik"); zakupy.add("Kakao"); zakupy.add("Miód"); rozszerzalna tablica for (int i = 0; i < zakupy.size(); i++) { System.out.println(i + ". " + zakupy.get(i)); } Map ceny = new HashMap(); ceny.put("Mleko", new Double(1.49)); ceny.put("Rogalik", new Double(0.45)); odwzorowanie (mapa) System.out.println("Cena rogalika: " + ceny.get("Rogalik")); Object[] produkty = ceny.keySet().toArray(); for (int j = 0; j < produkty.length; j++) { System.out.println( produkty[j]+" : " + ceny.get(produkty[j])); } Klasy kolekcji na platformie Java Implementacje Tablica mieszająca Set HashSet Interfejsy List Map Rozszerzalna tablica Zbalansowane drzewo TreeSet ArrayList HashMap Lista wiązana LinkedList TreeMap Klasy kolekcji (c.d.) • Najprostszą klasą kolekcji jest ArrayList (jest to lista zaimplementowana w postaci tablicy). Zapewnia ona szybki dostęp swobodny do elementów i potrafi automatycznie powiększać swój rozmiar. W starej bibliotece kolekcji jej odpowiednikiem jest Vector. • Większą funkcjonalność posiada klasa LinkedList (jest to lista o szybkim dostępie sekwencyjnym do elementów). Także automatycznie powiększa swój rozmiar. Jest jednak stosunkowo powolna przy dostępie swobodnym. Posiada bardziej uniwersalny zestaw metod (np. addFirst() / addLast()). Przegląd metod kolekcji Wybrane metody interfejsu Collection: – – – – – – dodanie elementów – metoda add() odczyt ilości elementów – metoda size() pobranie elementu – metoda get() ustawienie elementu – opcjonalna metoda set() usunięcie elementu – opcjonalna metoda remove() zwrócenie tablicy z elementami kolekcji – metoda toArray() Wybrane metody interfejsu Map: – – – – – dołączenie elementu do klucza – metoda put() odczytanie elementu spod klucza – metoda get() odczyt zbioru wszystkich kluczy – metoda keySet() test czy klucz jest w kolekcji – metoda containsKey() test czy element jest w kolekcji – metoda containsValue() Iteratory Iterator jest obiektem umożliwiającym przemieszczanie się po elementach kolekcji, niezależnie od jej struktury • Obiekt iteratora dla kolekcji uzyskuje się wywołując jej metodę iterator(). Jest on gotów do zwrócenia pierwszego elementu ciągu. • Dla uzyskania następnego elementu kolekcji należy wywołać metodę next() iteratora. • Można sprawdzić, czy są jeszcze jakieś elementy w ciągu wywołując metodę hasNext() • Można usunąć ostatni zwrócony przez iterator element, stosując metodę remove() Przykład użycia iteratora class Krzeslo { /* ... */ } Kod działa równie dobrze, jeśli jest: List sala = new LinkedList(); List sala = new ArrayList(); for (int i = 1; i <= 20; i++) { sala.add(new Krzeslo("nr. " + i)); } odczyt iteratora Iterator it = sala.iterator(); czy są jeszcze elementy? while (it.hasNext()) { Krzeslo k = (Krzeslo) it.next(); System.out.println(k.odczytajNumer()); przywrócenie typu } (rzutowanie) • Użycie iteratorów pozwala korzystać z kolekcji w taki sposób, że łatwo wymienić jej implementację na inną Wyjątki Podstawą filozofii Javy jest założenie, że: źle sformułowany kod nie zostanie wykonany • Sytuacja błędna powoduje utworzenie nowego obiektu wyjątku i rzucenie go • Kod, który wyrzuca wyjątki można spróbować wykonać, a pojawiające się wyjątki można łapać • Złapany wyjątek można obsłużyć albo rzucić ponownie • Jeśli kod w metodzie rzuca wyjątek (nie jest on obsłużony), musi być to wyspecyfikowane w deklaracji metody • Wszystkie wyjątki pochodzą od klasy java.lang.Exception Łapanie wyjątków • Odbywa się z użyciem bloków try i catch. – Pierwszy blok (try – spróbuj) obejmuje kod, który może spowodować wyjątek. – Drugi blok (catch – złap) określa jakie wyjątki chce obsłużyć i zawiera kod z obsługą błędów. • Jeśli wyjątek został złapany, można m.in. – wyświetlić informację o pochodzeniu błędu (jest to najczęściej używane): e.printStackTrace() – odczytać komunikat błędu: e.getMessage() – odczytać źródło błędu: e.getCause() Łapanie wyjątków – przykład (1) import java.io.*; public class Imie { static void odczytajImie() { BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); try { System.out.print("Podaj imie: "); String imie = in.readLine(); // może być wyjątek blok try System.out.print("Witaj " + imie + "!"); } catch (IOException e) { e.printStackTrace(); lokalna obsługa wyjątku } } public static void main(String[] args) { odczytajImie(); } } Łapanie wyjątków – przykład (2) import java.io.*; specyfikacja wyjątku public class Imie { static void odczytajImie() throws IOException { BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); System.out.print("Podaj imie: "); String imie = in.readLine(); // może być wyjątek System.out.print("Witaj " + imie + "!"); } public static void main(String[] args) { try { odczytajImie(); blok try } catch (IOException e) { e.printStackTrace(); obsługa wyjątku } } } Rzucanie wyjątków • Aby zasygnalizować błąd, należy utworzyć obiekt wyjątku (podając przyczynę błędu) • Następnie należy rzucić wyjątek, z użyciem słowa kluczowego throw: long suma = 0; int iloscNieujemnych = 0; void dodajNieujemna(int liczba) throws IllegalArgumentException { if (liczba > 0) { suma += liczba; iloscNieujemnych++; } else throw new IllegalArgumentException("liczba <= 0"); } Tworzenie własnych klas wyjątków • Nowa klasa wyjątku powinna dziedziczyć z Exception • Powinna deklarować przynajmniej dwa konstruktory: • bezparametrowy • z jednym argumentem typu String • Może zawierać dodatkowe pola i metody public class AccessDeniedException extends Exception { public AccessDeniedException() { super(); } public AccessDeniedException(String s) { super(s); } } Strumienie wejścia/wyjścia • • • • Typy strumieni Łączenie strumieni dla zwiększenia możliwości Różne źródła i ujścia danych Przykłady użycia Strumienie danych Strumień jest abstrakcyjną reprezentacją dowolnego źródła lub ujścia danych • Na podstawie dwóch logicznych operacji wyróżniamy dwa typy strumieni: – strumienie wejściowe (odczyt danych) – strumienie wyjściowe (zapis danych) • Strumień jest zawsze stowarzyszony z jakimś źródłem danych (odczyt) lub ujściem (zapis) • Wyróżniamy także strumienie bajtowe i znakowe • W Javie obsługę strumieni zapewnia pakiet java.io Strumienie wejściowe i wyjściowe strumień wejściowy źródło danych informacja odczyt Program strumień wyjściowy Program zapis informacja ujście danych Algorytmy odczytu i zapisu Odczyt Zapis otwórz strumień jeśli jest jeszcze informacja odczytaj informację zamknij strumień otwórz strumień jeśli jest jeszcze informacja zapisz informację zamknij strumień Hierarchia strumieni Strumienie bajtowe Strumienie znakowe • Klasy strumieni są w pakiecie java.io • Przy nieudanych operacjach pojawiają się wyjątki klasy java.io.IOException • Strumienie bajtowe i znakowe zapewniają praktycznie taką samą funkcjonalność Strumienie bajtowe Dwa najbardziej ogólne strumienie bajtowe to: • InputStream – do odczytu, oraz • OutputStream – do zapisu ciągów bajtów. InputStream OutputStream abstract int read() abstract void write(int b) int read(byte[] b) void write(byte[] b) int read( byte[] b, int off, int len) void write( byte[] b, int off, int len) long skip(long n) void flush() void close() void close() Strumienie znakowe Podstawowymi strumieniami znakowymi są: • Reader – do odczytu, oraz • Writer – do zapisu ciągów znaków. Reader Writer int read() void write(int c) int read(char[] cbuf) void write(char[] cbuf) abstract int read(char[] cbuf, int off, int len) abstract void write(char[] cbuf, int off, int len) long skip(long n) void flush() void close() void close() Rozszerzanie funkcjonalności • Zwiększenie funkcjonalności odbywa się przez użycie wyspecjalizowanych strumieni. Korzystają one z innych strumieni, aby wykonywać swoją pracę. • Robi się to przez opakowanie jednego typu strumienia w inny (aby to zadziałało musi istnieć w wybranej klasie odpowiedni konstruktor). InputStream InputStreamReader BufferedReader int read(byte[] b) int read(char[] cbuf) String readLine() Odczyt standardowego wejścia import java.io.*; public class PowielajLinie { public static void main(String[] args) throws IOException { System.out.println("Wpisuj linie tekstu. " + "Koniec gdy pusta linia."); BufferedReader in = new BufferedReader( new InputStreamReader(System.in)); InputStreamReader(System.in) System.in String linia; do { linia = in.readLine(); System.out.println(linia); } while (linia.length() > 0); } } InputStream Strumienie wyspecjalizowane Zamiana strumieni bajtowych na znakowe: • InputStreamReader / OutputStreamWriter Odczyt i zapis z uwzględnieniem znaków końca linii: • BufferedReader – readLine() • BufferedWriter – newLine() Strumień z buforem (większa wydajność): • BufferedInputStream / BufferedOutputStream Przeciążone metody println(): • PrintStream / PrintWriter Strumienie wyspecjalizowane Odczyt i zapis tablic bajtów lub znaków (w pamięci): • ByteArrayInputStream / ByteArrayOutputStream • CharArrayReader / CharArrayWriter Odczyt i zapis łańcuchów napisów (w pamięci): • StringReader / StringWriter Odczyt i zapis plików: • FileInputStream / FileOutputStream • FileReader / FileWriter Serializacja obiektów: • ObjectInputStream / ObjectOutputStream Odczyt i zapis plików • Żeby otworzyć plik, wystarczy stworzyć obiekt wybranej klasy, podając nazwę pliku w postaci łańcucha znaków lub obiektu klasy File FileInputStream – odczyt pliku binarnego FileOutputStream – zapis pliku binarnego (*) FileReader – odczyt pliku znakowego FileWriter – zapis pliku znakowego (*) (*) możliwe otwarcie pliku do dopisywania, bez usuwania poprzedniej zawartości • Otwarty strumień można opakować w wybrany strumień wyspecjalizowany • Po zakończeniu strumień należy zamknąć – close() Klasa File • NIE JEST to plik – służy raczej jako OPIS ŚCIEŻEK do plików lub katalogów: File plik = new File("plik.txt"); FileReader in = new FileReader(plik); • Przy użyciu tej klasy można także m.in. – sprawdzać informacje o pliku: isDirectory(), isFile(), canRead(), canWrite(), exists(), length(), lastModified() – wykonywać operacje na plikach i katalogach: (delete(), mkdir(), mkdirs(), renameTo() – odczytywać zawartość katalogu: list(), listFiles()