Programowanie w środowisku graficznym- wykład 8 Kolekcje c.d. ( mapy) Strumienie Wątki-podstawy

Podobne dokumenty
Aplikacje w Javie- wykład 11 Wątki-podstawy

Programowanie w Javie - wykład 13 Kolekcje c.d. ( mapy)

Biblioteki wejścia/wyjścia. Strumienie we/wy (I/O)

Wykład 4: Wejście/wyjście: strumienie Java

Strumienie i serializacja

Podstawy i języki programowania

Kurs programowania. Wykład 10. Wojciech Macyna. 05 maja 2016

Aplikacje w Javie wykład 9 Strumienie

dr Krzysztof Podlaski

Java Zadanie 1. Aby poprawnie uruchomić aplikację desktopową, należy zaimplementować główną metodę zapewniającą punkt wejścia do programu.

JAVA. Strumienie wejścia i wyjścia. Pliki - zapis i odczyt

Wstęp do Java. Operacje Wejścia-Wyjścia Programowanie Wielowątkowe. dr Krzysztof Podlaski. Wydział Fizyki i Informatyki Stosowanej

Język JAVA podstawy. Wykład 6, część 2. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Strumienie, pliki. Sortowanie. Wyjątki.

Aplikacje w Javie wykład 8 Kolekcje c.d. (komparatory, kolejki, mapy)

Programowanie w języku Java. Kolekcje

STRUMIENIE DANYCH, SERIALIZACJA OBIEKTÓW

Kolekcje mgr Tomasz Xięski, Instytut Informatyki, Uniwersytet Śląski Katowice, 2011

java.util.* :Kolekcje Tomasz Borzyszkowski

Programowanie Obiektowe (Java)

Współbieżność i równoległość w środowiskach obiektowych. Krzysztof Banaś Obliczenia równoległe 1

Apilkacje w środowisku Java - wykład 8 Kolekcje c.d. ( mapy) Podstawy programowania funkcyjnego

STRUMIENIE TEKSTOWE WEJŚCIOWE WPROWADZANIE DANYCH STRUMIENIE BAJTOWE, STRUMIENIE TEKSTOWE

Kurs programowania. Wykład 9. Wojciech Macyna. 28 kwiecień 2016

Strumienie, pliki. Sortowanie. Wyjątki.

Java. Wykład. Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ

Dokumentacja do API Javy.

Podstawy otwartych języków programowania Wyjątki i strumienie I/O

Programowanie w języku Java WYKŁAD

Programowanie Obiektowe (Java)

Rozdział 7 Strumienie, operacje wejścia-wyjścia

Wielowątkowość. Programowanie w środowisku rozproszonym. Wykład 1.

Programowanie Obiektowe Java

Aplikacje w Javie wykład 8 Kolekcje c.d. (komparatory, kolejki, mapy)

Programowanie w języku Java - Wyjątki, obsługa wyjątków, generowanie wyjątków

Inynieria oprogramowania Lecture XXX. Java TM cz IV: IO. Bartosz Walter

Programowanie sieciowe

Java. Programowanie Obiektowe Mateusz Cicheński

Kolekcje. Na podstawie:

Język Java wątki (streszczenie)

Programowanie współbieżne i rozproszone

Platformy Programistyczne Zagadnienia sieciowe i wątki

Programowanie obiektowe

Tworzenie aplikacji w języku Java

JAVA I SIECI. MATERIAŁY:

Autor: dr inż. Zofia Kruczkiewicz, Programowanie aplikacji internetowych 1

Języki i metody programowania Java INF302W Wykład 3 (część 1)

Przygotował: Jacek Sroka 1. Java SE. Strumienie

Bezpieczne uruchamianie apletów wg

Aplikacja wielowątkowa prosty komunikator

Wykład 10: Wejście i Wyjście

Metody zawarte w klasie File: boolean createnewfile() tworzy nowy, pusty plik, ale tylko jeśli on wcześniej nie istniał. boolean delete() usuwa dany

Programowanie obiektowe

Wykład 2: Podstawy Języka

Język Java część 2 (przykładowa aplikacja)

Kurs programowania. Wykład 9. Wojciech Macyna

Wątki. Definiowanie wątków jako klas potomnych Thread. Nadpisanie metody run().

Programowanie Obiektowe Java

Java Collections Framework

Kolekcje - pakiet Java Collections Framework

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

Podstawy otwartych języków programowania Przechowywanie danych

1 Wątki 1. 2 Tworzenie wątków 1. 3 Synchronizacja 3. 4 Dodatki 3. 5 Algorytmy sortowania 4

Programowanie i projektowanie obiektowe

Dawid Gierszewski Adam Hanasko

Współbieżność w środowisku Java

Java niezbędnik programisty spotkanie nr 11. Importy statyczne, wejście/wyjście, wyrażenia regularne, serializacja

Java: kilka brakujących szczegółów i uniwersalna nadklasa Object

Aplikacje w Javie wykład 9 Strumienie

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Języki i metody programowania Java INF302W Wykład 2 (część 1)

Wykład 7: Pakiety i Interfejsy

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

Realizacja ekstensji klasy. Paulina Strzelecka, Tomasz Roszkowski

1 Klasa File. 2 Writer. Programowanie w j zyku Java - Adam Krechowicz. Klasa File zapewnia podstawowe operacje na plikach

Wielowątkowość mgr Tomasz Xięski, Instytut Informatyki, Uniwersytet Śląski Katowice, 2011

Multimedia JAVA. Historia

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji.

Prototype (prototyp) Cel: Przykład: Określenie rodzaju tworzonych obiektów poprzez wskazanie ich prototypu. Nowe instancje tworzymy kopiując prototyp.

Kolekcja (kontener) to po prostu obiekt, który grupuje wiele elementów w jeden twór.

Wątek - definicja. Wykorzystanie kilku rdzeni procesora jednocześnie Zrównoleglenie obliczeń Jednoczesna obsługa ekranu i procesu obliczeniowego

Platformy Programistyczne Podstawy języka Java

Programowanie obiektowe

Podejście obiektowe. Tablice obiektów Przykład 1 metody i atrybuty statyczne oraz niestatyczne

1 Atrybuty i metody klasowe

Język Java część 2 (przykładowa aplikacja)

Rozdział 4 KLASY, OBIEKTY, METODY

KOMUNIKACJA MIĘDZYPROCESOWA O B S Ł U G A WEJŚCIA/WYJŚCIA

PHP: bloki kodu, tablice, obiekty i formularze

Podstawy programowania skrót z wykładów:

Wykład 9 Kolekcje, pliki tekstowe, Przykład: Notatnik

Język JAVA podstawy. wykład 2, część 1. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Komunikacja z użyciem gniazd aplikacje klient-serwer

Aplikacje RMI. Budowa aplikacji rozproszonych. Część 2.

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

Zadanie polega na stworzeniu bazy danych w pamięci zapewniającej efektywny dostęp do danych baza osób.

Lab 9 Podstawy Programowania

Podstawowe części projektu w Javie

Java podstawy jęyka. Wykład 2. Klasy abstrakcyjne, Interfejsy, Klasy wewnętrzne, Anonimowe klasy wewnętrzne.

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej

Transkrypt:

Programowanie w środowisku graficznym- wykład 8 Kolekcje c.d. ( mapy) Strumienie Wątki-podstawy Treści prezentowane w wykładzie zostały oparte o: Barteczko, JAVA Programowanie praktyczne od podstaw, PWN, 2014 http://docs.oracle.com/javase/8/docs/ http://docs.oracle.com/javase/9/docs/ C. S. Horstmann, G. Cornell, Java. Podstawy, Helion, Gliwice 2013 C. S. Horstmann, Java, Techniki zaawansowane(wydanie10), Helion, Gliwice 2017 1

Mapy (interfejs Map) 7 Tablica asocjacyjna jest zestawem elementów, do których zapewniono swobodny, bezpośredni (czyli bez konieczności przeglądania elementów zestawu) dostęp za pomocą kluczy. Można sobie wyobrażać, że jest to uogólnienie zwykłej tablicy: w zwykłej tablicy kluczami są indeksy całkowite, a w tablicy asocjacyjnej kluczami mogą być dowolne obiekty. Efektywne realizacje tablic asocjacyjnych opierają się na odpowiedniej implementacji słowników (odwzorowujących klucze w odpowiadające im wartości). W Javie słownikowe implementacje tablic asocjacyjnych określane są słowem map, pochodzącym od terminu mapping, oznaczającego jednoznaczne odwzorowanie (w tym przypadku zbioru kluczy w zbiór wartości). Również po polsku krótko będziemy nazywać tablice asocjacyjne mapami.

Mapy Mapa jest jednoznacznym odwzorowaniem zbioru kluczy w zbiór wartości. O mapach możemy myśleć jako o takich kolekcjach par: klucz - wartość, które zapewniają odnajdywanie wartości związanej z podanym kluczem. Zarówno klucze, jak i wartości mogą być referencjami do dowolnych obiektów (jak również wartościami null). Oczywiście, wartości kluczy nie mogą się powtarzać (odwzorowanie musi być jednoznaczne). Natomiast pod różnymi kluczami można zapisać te same wartości (odwzorowanie nie musi być wzajemnie jednoznaczne). Przykłady: Zestaw danych zawierający: Klucz: NIP ->Wartość : Dane o podatniku Klucz: Kraj ->Wartość : Stolica Klucz: Nazwa kontaktu ->Wartość : Numer telefonu i pozwalający na odnajdywanie poszukiwanej wartości po kluczu. Istotą zastosowania map jest możliwość łatwego i jednocześnie szybkiego odnajdywania informacji w powiązanych zestawach danych. 8

Mapy - interfejs Map<K,V> 9 W JCF ze względu na specyfikę działania na mapach (jako zestawach par klucze-wartości) implementują one interfejs Map, a nie Collection. Podstawowe operacje na mapie (niezależnie od jej implementacji) są określone przez metody interfejsu Map<K,V>, gdzie K typ kluczy, V typ wartości. Należą do nich: void clear() - usuwa wszystkie elementy mapy (operacja opcjonalna). boolean containskey(object key) - zwraca true jeżeli mapa zawiera podany klucz (i związaną z nim wartość). boolean containsvalue(object value) - zwraca true jeśli mapa zawiera podaną wartość (do ktorej może prowadzić wiele kluczy). V get(object key) - zwraca wartość dla danego klucza. V put(k key, V value) - dodaje parę klucz-wartość do mapy (lub zastępuje poprzednie odwzorowanie tego klucza podaną wartością), zwraca obiekt, który uprzednio znajdował się pod danym kluczem lub null. void putall(map<...> t)- dodaje do mapy wszystkie elementy mapy t. V remove(object key) - usuwa odwzorowanie: klucz-wartość z tej mapy (operacja opcjonalna). Zwraca usuniętą wartość. int size() - zwraca liczbę par klucz-wartość w mapie Uwaga. Metody put..., ze względu na jednoznaczność, powodują zastąpienie istniejących w mapie odwzorowań, które mają takie same klucze jak dodawane odwzorowania.

Mapy - interfejs Map<K,V> Metoda containskey(..) wydaje się duplikować jakby działanie metody get(..). Ale pamietajmy, że mapy mogą zawierać pod danym kluczem wartość null. Wtedy metoda get zwraca null i nie wiemy czy oznacza to brak odwzorowania w mapie, czy też odwzorowanie klucza w wartość null. Metoda containskey pozwala to rozstrzygnąć: zwróci true jeśli odwzorowanie jest (nawet jeśli wartością "pod" kluczem jest null), natomiast zwrócona wartość false świadczy na pewno o tym, że w mapie nie ma poszukiwanego odwzorowania. Ponadto w interfejsie Map istnieją metody : Set<K> keyset() - zwraca widok na zbiór kluczy. Collection<V> values() - zwraca widok na kolekcję wartości. Set<Map.Entry<K,V>> entryset() - zwraca widok na zbiór par kluczewartości jako zbiór obiektów typu Map.Entry<K,V>. K jest typem klucza, V - typem wartości. Ze względu na to, że mapy nie implementują interfejsu Collection, nie można od nich uzyskać bezpośrednio iteratorów. Można natomiast uzyskać kolekcje, które są widokami na zestawy kluczy (Set, bo bez powtórzeń), zestawy wartości (Collection, bo bez porządku i z możliwością powtórzeń elementów), oraz par klucze-wartośći (Set, bo bez powtórzeń). Od tych kolekcji - naturalnie - możemy uzyskać iteratory. 10

Mapy konkretne implementacje W mapach istotne jest szybkie odnajdywanie kluczy. Klucze (poprzez umieszczenie ich razem z wartościami w odpowiednich strukturach danych) związane są już bezpośrednio i niejako natychmiastowo z wartościami. Podobnie zatem jak w przypadku zbiorów istnieją dwie podstawowe implementacje, pozwalające na szybkie wyszukiwanie kluczy - implementacja oparta na tablicy mieszającej (HashMap) oraz na drzewie czerwono-czarnym (TreeMap). Niejako ubocznym skutkiem zastosowania tej ostatniej jest możliwość przeglądania kluczy mapy w naturalnym porządku rosnącym lub w porządku, określanym przez komparator podany przy konstrukcji mapy. Reguły stosowania komparatorów są takie same jak w przypadku zbiorów uporządkowanych. Mówimy, że mamy do czynienia z mapą uporządkowaną, a zatem mamy - tak jak w przypadku zbiorów uporządkowanych - dodatkowe właściwości takie jak "pierwszy" i "ostatni" klucz lub ich podzbiór "od" - "do" (określane przez metody interfejsu SortedMap) oraz operacje znajdowania bliskich kluczy (z interfejsu NavigableMap) Implementacja LinkedHashMap pozwala na odtworzenie kolejności dodawania elementów do mapy. 12

Mapy HashMap przykład 1 Przykład. Wyobraźmy sobie, że w pliku znajdują się nazwy i adresy firm. Nasz program po wczytaniu pliku ma za zadanie dostarczenie prostego interfejsu wyszukiwania adresu dla podanej nazwy firmy. Zauważmy, że proste sposoby rozwiązania tego problemu (np. prowadzenie dwóch list - nazw i adresów - i liniowe wyszukiwanie nazwy na liście nazw po to by otrzymać pozycję adresu na liście adresów) są bardzo nieefektywne. Klasa HashMap, która reprezentuje zestaw par klucz-wartość, efektywnie pozwala rozwiązać ten problem. Zatem jeśli postać pliku wejściowego jest następująca: nazwa_firmy1 adres nazwa_firmy2 adres... nazwa_firmyn adres to problem wyszukiwania adresów dla firm podawanych w dialogach wejściowych można oprogramować w następujący sposób: 13

import java.util.*; import java.io.*; import javax.swing.*; Mapy HashMap przykład 1 class TestMap1 { public static void main(string args[]) throws IOException { // mapa odwzorowań : nazwa -> adres // argumenty typu są dwa: dla klucza(nazwy) i wartości(adresu) Map<String, String> map = new HashMap<>(); // Wczytywanie danych Scanner scan = new Scanner(new File("firmsAddr.txt")); String firmname; String address; while (scan.hasnextline()) { firmname = scan.nextline(); address = scan.hasnextline()? scan.nextline() : ""; //nazwa firmy to klucz pod którym w mapie będzie jej adres map.put(firmname, address); //dodanie pary klucz-wartość do mapy 14

Mapy HashMap przykład 1 // Interakcyjna część programu: dla podanej w dialogu // nazwy firmy pokazywany jest jej adres while ((firmname = JOptionPane.showInputDialog ("Nazwa firmy"))!= null) { address = map.get(firmname); if (address == null) address = "?"; JOptionPane.showMessageDialog(null, "Firma: " + firmname + '\n' + "Adres: " + address); w Javie 8 dla map dostępna jest metoda: getordefault(key, value), która zwraca wartość pod kluczem key, a jeśli jej nie ma (w mapie brak klucza i wartości z nim związanej), to zwraca domyślną wartość value. Używając tej metody powyższy fragment programu mógłby wyglądać następująco: while ((firmname = JOptionPane.showInputDialog ("Nazwa firmy"))!= null) { address = map.getordefault(firmname, "?"); JOptionPane.showMessageDialog(null, "Firma: " + firmname + '\n' + "Adres: " + address); 15

Mapy keyset() i values() przykład 1 16 Metody keyset() i values() zwracają odpowiednio zbiór kluczy i kolekcję wartości mapy. Iterując po kluczach możemy sięgać po kolejne pary klucz-> wartość. Dla mapy map firma-> adres: System.out.println(map); Set<String> keys = map.keyset(); System.out.println("Klucze: " + keys); Collection<String> vals = map.values(); System.out.println("Wartości: " + vals); Set<Map.Entry<String,String>> entries = map.entryset(); System.out.println("Pary: " + entries); for (String name : map.keyset()){ String addr = map.get(name); System.out.println(name + " - adres: " + addr); Uzyskana poprzez keyset() kolekcja kluczy jest widokiem (a nie jakimś nowym obiektem-zbiorem) na klucze mapy, co oznacza, że operując na elementach tego zbioru faktycznie operujemy na kluczach mapy. Podobne widoki są zwracane poprzez values() i entryset(). Widok na klucze jest typu wyznaczanego przez klasę implementującą interfejs Set, ale jest to inna klasa niż znane nam HashSet i TreeSet

Mapy TreeMap przykład 1 Parę klucz-> wartość w mapie można usunąć za pomocą metody remove(key) albo za pomocą metody remove() iteratora zbioru kluczy. System.out.println(map); map.remove("microsoft"); System.out.println(map); map.put("microsoft", "Redmont"); System.out.println(map); for (Iterator<String> it = map.keyset().iterator(); it.hasnext(); ) { if(it.next().equals("microsoft")) it.remove(); Jeśli chcemy mieć posortowane klucze tworzymy TreeMap //mapa w porządku naturalnym (klucze alfabetycznie) Map<String, String> mapsort = new TreeMap<>(map); // Pusta mapa z komparatorem odwracającym // naturalny porządek Map<String, String> mapro = new TreeMap<>(Collections.reverseOrder()); // Wpisujemy do niej pary z mapy map mapro.putall(map); System.out.println(mapRo); 17

Mapy entryset - przykład 2 Efektywniejszym od keyset()(gdyż keyset wymaga każdorazowego pobierania wartości spod klucza) sposobem przeglądania map jest użycie metody entryset. Uzyskana kolekcja par klucze-wartości (widok na klucze-wartości) jest typu wyznaczanego przez klasę implementującą interfejs Map.Entry (wewnętrzny interfejs interfejsu Map). Dzięki temu - ale tylko w trakcie iteracji po elementach tej kolekcji - mamy dodatkowe możliwości działania, a mianowicie: uzyskanie klucza (metoda getkey()), wartości dla danej pary (metoda getvalue()) oraz zmianę wartości dla danej pary (metoda setvalue(object). Przykład: Map<String, Integer> map = new HashMap<>(); for(char c = 'a'; c <= 'd'; c++) map.put("" + c, (int) c); Set<Map.Entry<String, Integer>> entries = map.entryset(); System.out.println(entries);//[a=97, b=98, c=99, d=100] //Efektywny sposób przeglądania map for(map.entry<string, Integer> e : entries) { Integer val = e.getvalue(); System.out.print(e.getKey() + " " + val + "' " ); //a 97, b 98, c 99, d 100, e.setvalue(val + 1000); System.out.println(entries);//[a=1097, b=1098, c=1099, d=1100] 18

Mapy przykład 3 Rozważmy napisanie programu diagnostycznego (np. jakichś napraw), który na podstawie wprowadzonego przez użytkownika symptomu podaje niezbędną akcję do wykonania. Możemy to zrobić np. przy pomocy instrukcji switch z ilością case odpowiadającą ilości symptomów (np. 100). Takie rozwiązanie jest niefektywne, wszelkie zmiany powodują konieczność modyfikacji kodu i ponownej kompilacji. Lepiej będzie oddzielić dane od kodu źródłowego (zapisać je w pliku np. w kolejnych wierszach symptom-akcja) i zastosować mapę. HashMap<String, String> diagmap = new HashMap<>(); for (Scanner sc = new Scanner(new File("diagnose.txt")); sc.hasnextline(); ) { diagmap.put(sc.nextline(), sc.nextline()); String symptom = showinputdialog("podaj symptom"); String act = diagmap.containskey(symptom)? diagmap.get(symptom) : "Nieznany symptom"; showmessagedialog(null, act); Zamiast wielu linijek kodu mamy kilka, a program jest niezależny od zestawu symptomów i odpowiednich akcji. Istotą zastosowania map jest możliwość szybkiego odnajdywania informacji w powiązanych zestawach danych, a także tworzenia zwięzłego i elastycznego kodu. 19

Klasa Collections i Arrays JCF zawiera dwie użyteczne klasy, wspomagające działania na kolekcjach (a także tablicach) - klasę Collections i klasę Arrays. Klasa Collections dostarcza statycznych metod, operujących na kolekcjach lub zwracających kolekcje. Do metod tych należą: algorytmy kolekcyjne, metody zwracające widoki kolekcji, metody tworzące specjalne kolekcje. Wybrane algorytmy kolekcyjne: static int binarysearch(list<... > list, K key)- wyszukiwanie binarne na liście. Zwraca indeks elementu, który jest równy podanemu obiektowi key (wedle metody compareto z klas(y) elementów listy, przy czym zarówno elementy listy, jak i obiekt key muszą być porównywalne). Lista musi być najpierw posortowana według naturalnego porządku np. metodą Collections.sort(List l). Przy braku elementu zwraca wartość < 0 (a nie -1!) static int binarysearch(list<...> list, K key, Comparator<...> c) - wyszukiwanie binarne według podanego komparatora (porównywanie obiektu key z elementami listy następuje za pomocą metody compare komparatora). Lista musi być posortowana zgodnie z porządkiem definiowanym przez komparator np. metodą Collections.sort(List l, Comparator c). Zwraca indeks odnalezionego elementu lub wartość < 0, gdy brak jest elementu. 20

Klasa Collections i Arrays 21 static void sort(list<t> list)- sortowanie listy według naturalengo porządku elementów (rosnąco) static void sort(list<t> list, Comparator<...> c) - sortowanie listy wedle porządku określanego przez komparator. W klasie Arrays dostarczono statycznych metod, m.in. implementujących algorytmy sortowania i binarnego wyszukiwania dla tablic zarówno typów pierwotnych, jak i obiektów (w tym ostatnim przypadku również z możliwością użycia komparatorów). Metody klasy Collections zwracające widoki kolekcji nakładają na kolekcje "opakowania" w celu zmiany ich niektórych właściwości. W szczególności możemy uzyskać niemodyfikowalne oraz synchronizowane widoki dowolnej kolekcji. Przypomnijmy, że niemodyfikowalność kolekcji oznacza niemożliwość wszelkich modyfikacji (zarówno strukturalnych - dodawanie usuwanie elementów, jak i zmian wartości elementów). Czasami jest to potrzebne, gdy chcemy zagwarantować, by po utworzeniu kolekcja w ogóle nie mogła podlegać zmianom, lub gdy chcemy zróżnicować prawa dostępu do kolekcji dla różnych grup użytkowników naszej aplikacji (ktoś może zmieniać kolekcje, kto inny - tylko przeglądać). Gotowe implementacje kolekcji w JCF są modyfikowalne.

Klasa Collections i Arrays 22 Niemodyfikowalne widoki kolekcji możemy uzyskać za pomocą metod (dla ułatwienia pominięto parametry typu): public static Collection unmodifiablecollection(collection c); public static Set unmodifiableset(set s); public static List unmodifiablelist(list list); public static Map unmodifiablemap(map m); public static SortedSet unmodifiablesortedset(sortedset s); public static SortedMap unmodifiablesortedmap(sortedmap m); Szczególnym rodzajem niemodyfikowalności jest brak możliwości zmian rozmiaru kolekcji. Takich kolekcji (których elementy możemy zmieniać, ale nie możemy do nich dodawać nowych elementów ani usuwać istniejących) dostarcza statyczna metoda aslist z klasy Arrays, zwracająca listę, elementami której są elementy tablicy przekazanej jako argument. Uwaga: lista ta jest widokiem na tablicę! Metoda ta może służyć nie tylko do łatwego tworzenia list z istniejących tablic, ale również do tworzenia nowych, pustych list o niezmiennych rozmiarach: // Tworzy listę o ustalonym (i niezmiennym) // rozmiarze 1000 elementów List l = Arrays.asList(new Object[1000]);

Java 9 metoda of dla List, Set i Map 23 W Java 9 została dostarczona nowa statyczna metoda fabrykująca ( factory methods ) of tworząca instancje kolekcji (list i zbiorów) oraz map o małej liczbie elementów. // for empty list static <E> List<E> of() // for list containing one element static <E> List<E> of(e e1) // for list containing two element static <E> List<E> of(e e1, E e2) // // for list containing an arbitrary number of elements static <E> List<E> of(e... elements); Przykład: List<String> immutablelist = List.of(); immutablelist = List.of("one", "two", "three"); //jeśli dodamy null do listy to będzie wyrzucony wyjątek //NullPointerException //List<String> immutablelist = List.of("one", "two", null);

Java 9 metoda of dla List, Set i Map 24 Lista stworzona przy pomocy metody of jest niezmienialna (immutable), dlatego jeśli spróbujemy dodać element do listy zostanie wyrzucony wyjątek java.lang.unsupportedoperationexception List<String> immutablelist = List.of("one", "two", "three"); immutablelist.add("four"); //wyjatek Rozwiązanie problemu: List<String> mutablelist = new ArrayList<>(List.of("one", "two", "three")); mutablelist.add("four"); mutablelist.add(null); // Result: [one, two, three, four, null] Set // for empty Set static <E> Set<E> of() // for Set containing one element static <E> Set<E> of(e e1) // for Set containing two element static <E> Set<E> of(e e1, E e2) // // for Set containing an arbitrary number of elements static <E> Set<E> of(e... elements)

Java 9 metoda of dla List, Set i Map 25 Map Map.of() Aby stworzyć mapę korzystamy z metod statycznych: // for empty Map static <K, V> Map<K, V> of() // for Map containing a single mapping static <K, V> Map<K, V> of(k k1, V v1) // for Map containing two mappings static <K, V> Map<K, V> of(k k1, V v1, K k2, V v2) // // for Map containing up to ten mappings static <K, V> Map<K, V> of(k k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) Przykład: Map<Integer, String> immutablemap = Map.of(); immutablemap = Map.of(1, "one", 2, "two", 3, "three");

Java 9 metoda of dla List, Set i Map 26 Map.ofEntries() Jeśli chcemy stworzyć mapę z więcej niż 10 mapowaniami korzystamy z metody Map.ofEntries(). static <K, V> Map<K, V> ofentries( Entry<? extends K,? extends V>... entries) Aby z niej skorzystać pary klucz- wartość (entry) można stworzyć metodą static <K, V> Entry<K, V> entry(k k, V v) Np.: Map<Integer, String> newimmutablemap = Map.ofEntries(Map.entry(1, "one"), Map.entry(2, "two"), Map.entry(3, "three")); System.out.println(newImmutableMap);//{1=one, 3=three, 2=two Map<Integer, String> newimmutablemap1 = Map.ofEntries(); System.out.println(newImmutableMap1);//{

Strumienie Strumień danych oznacza ciąg danych, do którego dane mogą być dodawane i z którego dane mogą być pobierane. Przy czym: strumień związany jest ze źródłem lub odbiornikiem danych źródło lub odbiornik mogą być dowolne: plik, pamięć, zasoby sieciowe (poprzez URL), gniazdo, potok... strumień służy do zapisywania-odczytywania informacji - dowolnych danych Program: a) kojarzy strumień z zewnętrznym źródłem/odbiornikiem, b) otwiera strumień, c) dodaje lub pobiera dane ze strumienia, d) zamyka strumień. przy czytaniu lub zapisie danych z/do strumienia mogą być wykonywane dodatkowe operacje (np. buforowanie, kodowanie-dekodowanie, kompresjadekompresja) w Javie dostarczono klas reprezentujących strumienie. Hierarchia tych klas pozwala na programowanie w sposób niezależny od konkretnych źródeł i odbiorników. 27

java.io java.nio Java dostarcza dwóch podstawowych pakietów (z podpakietami), służących do przeprowadzania operacji wejścia-wyjścia: java.io java.nio Pakiet java.io zawiera przede wszystkim klasy, które pozwalają operować na strumieniach danych. W pakiecie java.nio ("Java new input-output", w skrócie NIO) wprowadzono dodatkowe środki wejścia-wyjścia, takie jak kanały, bufory i selektory. Mimo nazwy ("new input-output") środki te nie zastępują klas strumieniowych. Służą przede wszystkim do zapewnienia wysokiej efektywności i elastyczności programów, które w bardzo dużym stopniu obciążone są operacjami wejściawyjścia. W szczególności dotyczy to serwerów, które muszą równolegle obsługiwać ogromną liczbę połączeń sieciowych. Oprócz tego Java dostarcza klas reprezentujących inne od strumieni obiekty operacji wejścia-wyjścia. Do klas tych należy np. klasa File z pakietu java.io - opisująca pliki i katalogi, a także - w pakiecie java.net - klasy reprezentujące obiekty "sieciowe", takie jak URL czy gniazdo (socket), mogące stanowić źródło lub odbiornik danych w sieci (w szczególności w Internecie). Obiekty tych klas nie stanowią strumieni. Do operowania na nich strumienie (lub kanały) są jednak potrzebne i możemy je uzyskać przez użycie odpowiednich konstruktorów lub metod. 28

Klasy strumieniowe 29 Klasy strumieniowe można podzielić na grupy wg następujących kryteriów: klasy strumieni wejściowych klasy strumieni wyjściowych (Na strumieniach możemy wykonywać dwie podstawowe operacje: odczytywanie danych i zapisywanie danych. Z tego punktu widzenia możemy mówić o strumieniach wejściowych i wyjściowych) klasy dla strumieni bajtowych klasy dla strumieni znakowych (strumienie znakowe realizują przesyłanie znaków, które w Javie są znakami Unicodu, strumienie bajtowe przesyłają bajty danych) UWAGA: Przy przetwarzaniu tekstów należy korzystać ze strumieni znakowych ze względu na to, iż w trakcie czytania/pisania wykonywane są odpowiednie operacje dekodowania/kodowania ze względu na stronę kodową właściwą dla źródła/odbiornika klasy przetwarzające klasy przedmiotowe (klasy przetwarzające implementują określone rodzaje przetwarzania strumieni, niezależnie od źródła/odbiornika, klasy przedmiotowe są związane z konkretnymi rodzajami źródła/odbiornika)

Klasy strumieniowe Nadklasy, z których wywodzą się wszystkie inne klasy strumieni Wejście Wyjście Strumienie bajtowe InputStream OutputStream Strumienie znakowe Reader Writer Wszystkie powyższe klasy są abstrakcyjne i zawierają deklaracje podstawowych metod przetwarzania strumieni, które podklasy winny implementować. Przy tworzeniu obiektu-strumienia strumień jest automatycznie otwierany, czytanie read() (bajtów, znaków) - różne wersje tej (przeciążonej) metody pozwalają na przeczytanie jednego bajtu ze strumienia bajtowego lub znaku ze strumienia znakowego albo całej porcji bajtów/znaków, zapisywanie write() (bajtów/znaków) - różne wersje tej (przeciążonej) metody pozwalają zapisywać pojedyncze bajty/znaki lub tablice bajtów/znaków, a w przypadku strumieni znakowych również napisy (obiekty klasy String), pozycjonowanie strumieni (metody skip(..), mark(..), reset() ) - każdy strumień może być traktowany jako sekwencja bajtów/znaków, czytanie i zapisywanie zawsze dotyczy bieżącej pozycji tej sekwencji; po wykonaniu operacji czytania lub zapisu bieżąca pozycja jest zwiększana; metody pozycjonowania pozwalają zmieniać bieżącą pozycję. zamykanie strumieni (metoda close()) - strumień zawsze należy zamknąć po zakończeniu operacji na nim. 30

Klasy strumieniowe - przykład Metody te są zazwyczaj odpowiednio przedefiniowane w klasach dziedziczących, a polimorfizm zapewnia ich właściwe użycie Przykład. Stwórzmy ogólną klasę udostępniającą kopiowanie strumieni. import java.io.*; class StreamCopier { static void copy(inputstream in, OutputStream out) throws IOException { int c; while ((c = in.read())!= -1) out.write(c); static void copy(reader in, Writer out) throws IOException { int c; while ((c = in.read())!= -1) out.write(c); Uwaga: metoda read() zwraca liczbę całkowitą, reprezentującą kolejny znak ze strumienia znakowego (lub bajt ze strumienia bajtowego) albo wartość -1 gdy czytanie sięga poza koniec pliku. 31

Klasy strumieniowe - przykład 32 Możemy teraz użyć metody copy wobec dowolnych strumieni z odpowiednich konkretnych klas hierarchii klas strumieniowych, np. StreamCopier.copy(input, output); Po to by kopiowanie miało sens input musi oznaczać konkretne źródło danych, a output konkretny odbiornik danych. Strumień abstrakcyjny (w którymś momencie) musi być związany z konkretnym źródłem bądź odbiornikiem. W Javie jest to możliwe głównie (ale nie tylko) dzięki wprowadzeniu na kolejnych szczeblach dziedziczenia omawianych czterech hierarchii (we-wy, bajty-znaki) konkretnych klas oznaczających różne rodzaje źródła/odbiornika danych. Można by je nazwać klasami przedmiotowymi, bowiem mają one ustalone przedmioty operacji konkretne rodzaje źródła bądź odbiornika. Źródła bądź odbiorniki danych mogą być różnorodne. Strumień może być związany np. z plikiem, z pamięcią operacyjną, z potokiem, z zasobem sieciowym, z gniazdkiem (socket)... Klasy przedmiotowe wprowadzono dla wygody operowania na konkretnych rodzajach źródeł i odbiorników.

Klasy przedmiotowe 33 Źródło/odbiornik Strumienie znakowe Strumienie bajtowe CharArrayReader, CharArrayWriter ByteArrayInputStream, ByteArrayOutputStream Pamięć Potok Plik StringReader, StringWriter PipedReader, PipedWriter FileReader, FileWriter StringBufferInputStream PipedInputStream, PipedOutputStream FileInputStream, FileOutputStream

Klasy przedmiotowe - przykład Teraz już możemy użyć przykładowej (pokazanej poprzednio) klasy StreamCopier np. do kopiowania plików binarnych public class StreamCopy1 { public static void main(string[] args) { try { InputStream in1 = new FileInputStream("in.dat"); try { OutputStream out1 = new FileOutputStream("out.dat"); try { StreamCopier.copy(in1, out1); //kopiowanie finally { out1.close(); finally { in1.close(); catch (IOException exc) {//brak pliku lub błąd WE-WY System.err.println("I/O error: " + exc); System.exit(1); 34

Klasy przedmiotowe przykład - try-with-resources 35 Klauzulla finally jest wykonywana niezależnie od tego czy wystapi wyjątek czy nie, dlatego umieszczamy tam metodę close(). Dla uproszczenia, w Javie 7 wprowadzono instrukcję try-with-resources: try(otwarcie zasobu1; otwarcie zasobu2;...){ //przetwarzanie zasobów która powoduje automatyczne zamknięcie zasobów, zarówno przy normalnym zakończeniu ich przetwarzania, jak i w przypadku wyrzucenia wyjątku. try(filereader in1 = new FileReader("plik0.txt"); FileWriter out1 = new FileWriter("plik1.txt"); FileInputStream in2 = new FileInputStream("in"); FileOutputStream out2 = new FileOutputStream("out")){ StreamCopier.copy(in1, out1); StreamCopier.copy(in2, out2); catch(ioexception exc) {//brak pliku lub bład WE-WY System.err.println("I/O error: " + exc); System.exit(1);

Java 9 - try-with-resources 36 W Java 7 i 8 nie możemy w bloku try() używać żadnych zasobów (resources), które zostały zadeklarowane na zewnątrz Try-With-Resources. W razie potrzeby w bloku try() musieliśmy stworzyć duplikat void testtry_before_java9() throws IOException{ BufferedReader reader1 = new BufferedReader(new FileReader("dane.txt")); try (BufferedReader reader2 = reader1) { System.out.println(reader2.readLine()); W Java SE 9, jeśli mamy strumień zadeklarowany (jako final lub effectively final ) na zewnątrz Try-With-Resource, to nie musimy deklarować lokalnej kopii w bloku try(). Możemy użyć poprzednio stworzonego zasobu w Try-With-Resource Statement : void testrty_java9() throws IOException{ BufferedReader reader1 = new BufferedReader(new FileReader("dane.txt")); try (reader1) { System.out.println(reader1.readLine());

Klasy przedmiotowe Jedną z wersji konstruktorów klas strumieniowych związanych z plikami są konstruktory, w których podajemy jako argument nazwę pliku (można też podać referenecję do obiektu klasy File), przy tworzeniu obiektów klas strumieniowych związanych z plikami, odpowiednie pliki są otwierane; strumienie wejściowe są otwierane "tylko do odczytu", strumienie wyjściowe "tylko do zapisu". strumienie wyjściowe mogą być otwarte w trybie dopisywania (należy użyć konstruktora z drugim argumentem append ustawionym na true); w takim przypadku dane będą dopisywane do końca strumienia, przy operacjach na strumieniach może powstać wyjątek klasy IOException oznaczający błąd operacji (np. odczytu lub zapisu), a także wyjątki klas pochodnych FileNotFoundException (brak pliku) oraz EOFException (w trakcie operacji czytania lub pozycjonowania osiągnięto koniec pliku), przy obsłudze wyjątków wejścia-wyjścia czasami warto zastosować metodę printstacktrace(), która wyprowadza dokładne informacje o przyczynie i miejscu wystąpienia wyjątku. Użycie klas przedmiotowych nie jest jedynym sposobem związania logicznego strumienia z fizycznym źródłem lub odbiornikiem. Inne klasy (spoza pakietu java.io, np. klasy sieciowe) mogą dostarczać metod, które zwracają jako wynik referencję do strumienia związanego z konkretnym źródłem/odbiornikiem (np. plikiem w sieci). 37

Klasy przetwarzające Rodzaj przetwarzania Buforowanie Filtrowanie Konwersja: bajtyznaki Konkatenacja Strumienie znakowe BufferedReader, BufferedWriter FilterReader, FilterWriter InputStreamReader, OutputStreamWriter Strumienie bajtowe BufferedInputStream, BufferedOutputStream FilterInputStream, FilterOutputStream SequenceInputStream Serializacja obiektów Konwersje danych ObjectInputStream, ObjectOutputStream DataInputStream, DataOutputStream Zliczanie wierszy LineNumberReader LineNumberInputStream Podglądanie PushbackReader PushbackInputStream Drukowanie PrintWriter PrintStream 38

Klasy przetwarzające Buforowanie ogranicza liczbę fizycznych odwołań do urządzeń zewnętrznych. Filtrowanie: Klasy Filter... są klasami abstrakcyjnymi, definiującymi interfejs dla rzeczywistych filtrów. Filtrami są np.: DataInputStream i DataOutputStream, BufferedInputStream i BufferedOutputStream, LineNumberInputStream, PushbackInputStream, PrintStream, Można tworzyć własne filtry. Konwersje bajty-znaki: InputStreamReader czyta bajty ze strumienia definiowanego przez InputStream (strumień bajtowy) i zamienia je na znaki (16 bitowe), używając domyślnej lub podanej strony kodowej, OutputStreamWriter wykonuje przy zapisie konwersję odwrotną. Konkatenacja strumieni wejściowych pozwala połączyć strumienie i traktować je jak jeden strumień. Serializacja służy do "utrwalania" obiektów po to, by odtworzyć je w innym kontekście (przy ponownym uruchomieniu programu lub w innym miejscu, np. programie działającym w innym miejscu sieci po przekazaniu "utrwalonego" obiektu przez socket), 39

Klasy przetwarzające 40 Konwersje danych: DataInputStream i DataOutputStream pozwalają czytać/pisać dane typów pierwotnych (np. liczby rzeczywiste) w postaci binarnej. Strumienie są tutaj strumieniami binarnymi, w związku z tym koniec strumienia rozpoznaje się jako wyjątek EOFException. Zliczanie wierszy: LineNumber... zlicza wiersze strumienia przy czytaniu (i pozwala w każdym momencie uzyskać informację o numerze wiersza). Podglądanie: PushBack.. pozwala podglądnąć następny znak/bajt w strumieniu bez "wyciągania" tego znaku/bajtu. Drukowanie: Klasy Print... zawierają wygodne metody wyjścia (np. println). Niekoniecznie oznacza to drukowanie fizyczne, często wykorzystywane jest w powiązaniu z innymi strumieniami po to by łatwo wyprowadzać informacje. Konstruktory klas przetwarzających mają jako argument referencję do obiektów podstawowych klas abstrakcyjnych hierarchii dziedziczenia (InputStream, OutputStream, Reader, Writer). Dlatego przetwarzanie (automatyczna transformacja) danych jest logicznie oderwana od fizycznego strumienia, stanowi swoistą na niego nakładkę. Zatem zastosowanie klas przetwarzających wymaga: stworzenia obiektu związanego z fizycznym źródłem/odbiornikiem stworzenie obiektu odpowiedniej klasy przetwarzającej, "nałożonego" na fizyczny strumień.

Buforowanie Buforowanie ogranicza liczbę fizycznych odwołań do urządzeń zewnętrznych, dzięki temu, że fizyczny odczyt lub zapis dotyczy całych porcji danych, gromadzonych w buforze (wydzielonym obszarze pamięci). Jedno fizyczne odwołanie wczytuje dane ze strumienia do bufora lub zapisuje zawartość bufora do strumienia. W naszym programie operacje czytania lub pisania dotyczą w większości bufora (dopóki są w nim dane lub dopóki jest miejsce na dane) i tylko niekiedy powodują fizyczny odczyt (gdy bufor jest pusty) lub zapis (gdy bufor jest pełny). Np. przy czytaniu dużych plików tekstowych należy unikać bezpośredniego czytania za pomocą klasy FileReader. To samo dotyczy zapisu. Zastosowanie klasy BufferedReader (czy BufferedWriter) powinno przynieść poprawę efektywności działania programu. Ale klasa BufferedReader (BufferedWriter) jest klasą przetwarzającą, a wobec tego w jej konstruktorze nie możemy bezpośrednio podać fizycznego źródła danych. Np. przy czytaniu plików źródło podajemy przy konstrukcji obiektu typu FileReader, a po to, żeby uzyskać buforowanie, "opakowujemy" FileReadera BufferedReaderem. FileReader fr = new FileReader("plik.txt");//żródło BufferedReader br = new BufferedReader(fr);// dodajemy "opakowanie" // umożliwiające buforowanie String line;// czytamy wierszami: metoda readline zwraca wiersz // lub null jeśli koniec pliku while ((line = br.readline())!= null) { // kolejny wiersz pliku //... tu coś robimy z odczytanym wierszem 41

Buforowanie - przykład Przykład: program, czytający plik tekstowy i zapisujący jego zawartość do innego pliku wraz z numerami wierszy. import java.io.*; class Lines { public static void main(string args[]) { try (LineNumberReader lr = new LineNumberReader(new FileReader(args[0])); BufferedWriter bw = new BufferedWriter(new FileWriter(args[1]))) { String line; while ((line = lr.readline())!= null) { bw.write(lr.getlinenumber() + " " + line); bw.newline(); catch (IOException exc) { System.err.println(exc.toString()); System.exit(1); 42

Buforowanie 43 Klasa LineNumberReader dziedziczy klasę BufferedReader, dając możliwość prostego uzyskiwania informacji o numerze bieżącego wiersza (metoda getlinenumber()), do zapisu tekstu używana jest metoda write(string), zastosowanie metody newline() z klasy BufferedWriter pozwala w niezależny od platformy systemowej sposób zapisywać znak końca wierszy, przy zamknięciu (close) wyjściowego strumienia buforowanego zawartość bufora jest zapisywana do strumienia; istnieje też możliwość "ręcznego" opróżnianienia bufora przy pomocy metody void flush(), zapisującej dane, które pozostały w buforze, a nie zostały jeszcze zapisane w miejscu przeznaczenia. Działa ona dla wszystkich strumieni wyjściowych (bajtowych i znakowych).

Obiekty plikowe - klasa File Klasa File oznacza obiekty plikowe (pliki i katalogi). Jej metody umożliwiają m.in. uzyskiwanie informacji o plikach i katalogach, jak również wykonywanie działań na systemie plikowym. Wybrane metody klasy File boolean canread() - czy plik może być czytany? boolean canwrite()- czy plik może być zapisywany? boolean createnewfile() - tworzy nowy pusty plik static File createtempfile(string prefix, String suffix, File directory)- tworzy nowy plik tymczasowy z nazwą wg wzorca w podanym katalogu boolean delete() - usuwa plik lub katalog void deleteonexit() - zaznacza plik do usunięcia po zakończeniu programu boolean exists() - czy plik/katalog istnieje? String getname() - nazwa pliku lub katalogu String getparent() - katalog nadrzędny String getpath() - ścieżka boolean isdirectory() - czy to katalog? boolean isfile() - czy plik? 47

Obiekty plikowe - klasa File 48 boolean ishidden() - czy ukryty? long lastmodified() - czas ostatniej modyfikacji long length() - rozmiar String[] list() - lista nazw plików i katalogów w katalogu String[] list(filenamefilter filter) filtrowana lista nazw plików File[] listfiles() - lista plików i katalogów File[] listfiles(filefilter filter) - filtrowana lista plików i katalogów File[] listfiles(filenamefilter filter)- filtrowana lista plików i katalogów boolean mkdir() - tworzy katalog boolean renameto(file dest) zmienia nazwę/przenosi plik lub katalog. boolean setreadonly() - zaznacza jako "tylko od odczytu" URI touri()- tworzy obiekt klasy URI (Uniform Resource Identifier), reprezentujący ten obiekt plikowy FilenameFilter i FileFilter - interfejsy umożliwiające wybiórcze, wg dowolnie konstruowanych kryteriów, listowanie plików.

Programowanie współbieżne 51 Uruchomienie dowolnego programu (aplikacji) powoduje stworzenie procesu w systemie operacyjnym. Dla każdego procesu alokowane są przez system wymagane przez niego zasoby np. pamięciowe i plikowe. Proces - to wykonujący się program wraz z dynamicznie przydzielanymi mu przez system zasobami (np. pamięcią operacyjną, zasobami plikowymi) oraz, ewentualnie, innymi kontekstami wykonania programu (np. obiektami tworzonymi przez program). Systemy wielozadaniowe pozwalają na (teoretycznie) równoległe wykonywanie wielu procesów, z których każdy ma swój kontekst, w tym swoje zasoby. W systemach wielozadaniowych i wielowątkowych - jednostką wykonawczą procesu jest wątek. Każdy proces ma co najmniej jeden działający wątek, ale może też mieć ich wiele. Proces "posiada" zasoby i inne konteksty, wykonaniem "zadań" procesu zajmują się wątki (swego rodzaju podprocesy, wykonujące różne działania w kontekście jednego procesu). Wątki działają (teoretycznie) równolegle. Zatem równoległość działań w ramach procesu (jednego programu) osiągamy przez uruchamianie kilku różnych wątków. Wątek - to sekwencja działań, która może wykonywać się równolegle z innymi sekwencjami działań w kontekście danego procesu (programu).

Programowanie współbieżne 53 Proces wykonuje się poprzez wykonanie jego wątków. Zatem, zwolnienie procesora przez wątek jednego procesu i przydzielenie procesora wątkowi innego procesu wymaga "przeładowania" kontekstu procesu, bowiem każdy proces ma swój niezależny kontekst (np. przestrzeń adresową, odniesienia do otwartych plików). Podstawowa różnica pomiędzy procesami i wątkami polega na tym, że różne wątki w ramach jednego procesu mają dostęp do całego kontekstu tego procesu (m.in. przydzielonych mu zasobów). Wobec tego zamiana wątków jednego procesu "przy procesorze" jest wykonywana szybciej niż zamiana procesów (wątków różnych procesów). Z punktu widzenia programisty wspólny dostęp wszystkich wątków jednego procesu do kontekstu tego procesu ma zarówno zalety jak i wady. Zaletą jest możliwość łatwego dostępu do wspólnych danych programu. Wadą - brak ochrony danych programu przed równoległymi zmianami, dokonywanymi przez różne wątki, co może prowadzić do niespójności danych, a czego unikanie wiąże się z koniecznością synchronizacji działania wątków.

Tworzenie i uruchamianie wątku (klasa Thread) Uruchamianiem i zarządzaniem wątkami w Javie zajmuje się klasa Thread. Aby uruchomić wątek należy stworzyć obiekt klasy Thread i użyć metody start() wobec tego obiektu. Kod, wykonujący się w wątku (sekwencja działań, wykonująca się równolegle z innymi działaniami programu) określany jest przez obiekt klasy implementującej interfejs Runnable. Interfejs ten zawiera deklarację metody run(), w której przy implementacji zapisujemy kod. Ten kod będzie wykonywany w wątku (równolegle z innymi fragmentami wykonującymi się w innych wątkach). Metoda run() określa co ma robić wątek. Klasa Thread implementuje interfejs Runnable (podając "pustą" metodę run). Pierwszy sposób tworzenia i uruchamiania wątku 1) Zdefiniować własną klasę dziedziczącą Thread (np. class Timer extends Thread) 2) Przedefiniować odziedziczoną metodą run(), podając w niej działania, które ma wykonywać wątek 3) Stworzyć obiekt naszej klasy (np. Timer timer = new Timer(...); 4) Wysłać mu komunikat start() (np. timer.start()) 54

Tworzenie i uruchamianie wątku (klasa Thread) 55 Niech klasa Timer służy do zliczania czasu (nie mylić tej klasy z klasami Timer z pakietu javax.swing oraz Timer z pakietu java.util; tutaj pod tą nazwą opisujemy własną klasę - licznik czasu). public class Timer extends Thread { public void run() { int time = 0; //licznik sekund while (true) { try { this.sleep(1000); //usypiamy wątek na 1 sekundę catch(interruptedexception exc) { System.out.println("Wątek zliczania czasu zoostał przerwany."); return; time++; int minutes = time/60; int sec = time%60; System.out.println(minutes + ":" + sec);

Tworzenie i uruchamianie wątku (klasa Thread) 56 Kodu zliczającego upływ czasu dostarczyliśmy w metodzie run(). Zmienna time jest licznikiem sekund. W pętli while usypiamy wątek, wykonujący tę metodę run(), na 1000 milisekund, czyli 1 sekundę (statyczna metoda sleep z klasy Thread, która usypia bieżący wątek), po czym zwiększamy licznik sekund (zmienna time) i wyprowadzamy informację o upływie czasu na konsolę. W trakcie uśpienia wątek jest odsuwany od procesora (kod metody run nie wykonuje się wtedy). Dzięki temu uzyskujemy pożądany efekt. Metoda sleep może zgłaszać wyjątek InterruptedException, który powstaje na skutek przerwania działania wątku (metodą interrupt()). Dlatego musimy obsługiwać ten wyjątek. Przykład. Licznik czasu możemy zastosować np. w programie, który wymaga od użytkownika podania wszystkich stolic (lądowych) sąsiadów Polski. Wraz z podawaniem kolejnych stolic chcemy wypisywać na konsoli informację o upływającym czasie.

Tworzenie i uruchamianie wątku (klasa Thread) 57 import java.util.*; import static javax.swing.joptionpane.*; public class Quiz { public static void main(string args[]) { // Stolice do odgadnięcia Set<String> cap = new HashSet<>( Arrays.asList("praga", "bratysława","moskwa", "berlin", "kijów", "wilno", "mińsk")); //stolice już podane Set<String> entered = new HashSet<>(); showmessagedialog(null, "Podaj stolice lądowych sąsiadów Polski"); String askmsg = "Wpisz kolejną stolicę:" ; int count = 0; // ile podano prawidłowych odpowiedzi // Uruchomienie wątku zliczającego //i pokazującego upływający czas new Timer().start();

Tworzenie i uruchamianie wątku (klasa Thread) 58 // dopóki nie podano wszystkich stolic for (int n=cap.size(); count<n;) { String in = JOptionPane.showInputDialog("Odpowiedzi:" + count + '/'+ n +'\n' +askmsg); if (in == null) break; in = in.tolowercase(); // jeśli tej stolicy wcześniej nie podano // i odp. jest prawidłowa if (!entered.contains(in) && cap.contains(in)){ count++; entered.add(in);

Tworzenie i uruchamianie wątku (klasa Thread) 59 W pętli pobieramy dane od użytkownika, dopóki nie wpisze on prawidłowo wszystkich znajdujących się w zbiorze cap stolic lub nie przerwie wpisywania, wybierając w dialogu Cancel. Ponieważ wcześniej utworzyliśmy i uruchomiliśmy wątek zliczania czasu: Timer tm = new Timer(); tm.start(); lub zwięźlej: new Timer().start(); to nasz program równolegle wykonuje dwa zadania: interakcję z użytkownikiem (pytania o stolice) oraz wypisywanie na konsoli informacji o upływającym czasie. Wywołanie metody start() na rzecz obiektu klasy Thread powoduje uruchomienie metody run() z klasy Timer. To wywołanie nie blokuje głównego programu; wykonywane są dalsze jego instrukcje, a kod metody run() działa równolegle.

Tworzenie i uruchamianie wątku (interfejs Runnable) 60 Jak już wiemy, kod wykonywany przez wątek podajemy w metodzie run(). A metoda run() może być zdefiniowana w dowolnej klasie implementującej interfejs Runnable. Klasa Thread dostarcza zaś konstruktora, którego argument jest typu Runnnable. Konstruktor ten tworzy wątek, który będzie wykonywał kod zapisany w metodzie run() w klasie obiektu, do którego referencję przekazano wspomnianemu wyżej konstruktorowi. Drugi sposób tworzenia i uruchamiania wątków. 1) Zdefiniować klasę implementującą interfejs Runnable (np. class X implements Runnable). 2) Dostarczyć w niej definicji metody run() (co ma robić wątek). 3) Utworzyć obiekt tej klasy (np. X x = new X(); ) 4) Utworzyć obiekt klasy Thread, przekazując w konstruktorze referencję do obiektu utworzonego w p.3 (np.thread thread = new Thread(x);). 5) Wywołać na rzecz nowoutworzonego obiektu klasy Thread metodę start ( thread.start();)

Tworzenie i uruchamianie wątku (interfejs Runnable) 61 Oprogramowanie uprzednio omawianego licznika czasu przy użyciu drugiego sposobu: class Timer implements Runnable { public void run() { int time = 0; while (true) { try { Thread.sleep(1000); catch(interruptedexception exc) { System.out.println("Wątek zliczania czasu został przerwany."); return; time++; int minutes = time/60; int sec = time%60; System.out.println(minutes + ":" + sec);

Tworzenie i uruchamianie wątku (interfejs Runnable) Wówczas utworzenie i uruchomienie wątku zliczającego czas (w innej klasie): Timer tm = new Timer(); Thread thread = new Thread(tm); thread.start(); lub zwięźlej: new Thread(new Timer()).start(); Jak poprzednio, w metodzie run() dla uśpienia wątku na sekundę używamy statycznej metody sleep z klasy Thread, która usypia bieżący wątek (w tym przypadku ten, w którym wykonuje się ta metoda run()). Nie mogliśmy tym razem napisać this.sleep(), bo nowa klasa Timer nie dziedziczy już klasy Thread. Drugi sposób tworzenia i uruchamiania wątków ma pewne zalety w stosunku do korzystania wyłącznie z klasy Thread (czyli sposobu pierwszego): niekiedy daje lepsze możliwości separowania kodu (kod odpowiedzialny za pracę wątku może być wyraźnie wyodrębniony w klasie implementującej Runnable). w niektórych okolicznościach - mianowicie, gdy chcemy umieścić metodę run() w klasie, która dziedziczy jakąś inną klasę - jest jedynym możliwym sposobem. 62