Apilkacje w środowisku Java - wykład 10 Strumienie

Podobne dokumenty
Aplikacje w Javie wykład 9 Strumienie

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

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

Strumienie i serializacja

Aplikacje w Javie wykład 9 Strumienie

Podstawy i języki programowania

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

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

STRUMIENIE DANYCH, SERIALIZACJA OBIEKTÓW

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

Strumienie, pliki. Sortowanie. Wyjątki.

Programowanie w języku Java WYKŁAD

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

Programowanie Obiektowe (Java)

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

dr Krzysztof Podlaski

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

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

Aplikacje w Javie wykład 10 Strumienie (Klasa Files, Formatter, serializacja obiektów) Wątki (tworzenie i uruchamianie, zadania i wykonawcy)

Programowanie Obiektowe Java

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

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

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

MATERIAŁY POMOCNICZE DO ĆWICZENIA 3 Klasy i obiekty; atrybuty i metody

Strumienie, pliki. Sortowanie. Wyjątki.

Przygotował: Jacek Sroka 1. Java SE. Strumienie

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

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

Programowanie obiektowe

Programowanie obiektowe

Programowanie Obiektowe Java

Wykład 2: Podstawy Języka

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

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

Platformy Programistyczne Zagadnienia sieciowe i wątki

Podstawy i języki programowania

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

Języki i metody programowania Java INF302W Wykład 4

K O M U N I K A C J A MIĘDZYPROCESOWA O B S Ł U G A WEJŚCIA/WYJŚCIA

Programowanie obiektowe

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

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

Programowanie proceduralne INP001210WL rok akademicki 2018/19 semestr letni. Wykład 6. Karol Tarnowski A-1 p.

Dokumentacja do API Javy.

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 12. Karol Tarnowski A-1 p.

Programowanie obiektowe

Polimorfizm, metody wirtualne i klasy abstrakcyjne

Rozdział 4 KLASY, OBIEKTY, METODY

Wykład 4. Tablice. Pliki

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

MATERIAŁY POMOCNICZE DO ĆWICZENIA 1 Aplikacja w języku Java. Pakiety i biblioteki Javy. Kompilacja i wykonanie programu Javy

Programowanie obiektowe

Pliki. Operacje na plikach w Pascalu

Programowanie obiektowe

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

PARADYGMATY PROGRAMOWANIA Wykład 4

Wykład 2. Strumienie tekstowe (wprowadzanie danych z klawiatury) i bajtowe, otwieranie strumieni poprzez sieć - obiekty URL

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

JAVA I SIECI. MATERIAŁY:

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Biblioteka standardowa - operacje wejścia/wyjścia

Metody Metody, parametry, zwracanie wartości

Programowanie w Javie wykład 9 Klasy wewnętrzne, klasy anonimowe Klasy opakowujące

Obszar statyczny dane dostępne w dowolnym momencie podczas pracy programu (wprowadzone słowem kluczowym static),

Języki i Techniki Programowania II. Wykład 6. Wejście/Wyjście

Bezpieczne uruchamianie apletów wg

Programowanie w Internecie. Java

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

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

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

Wykład 7: Pakiety i Interfejsy

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak

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

Sposoby tworzenia projektu zawierającego aplet w środowisku NetBeans. Metody zabezpieczenia komputera użytkownika przed działaniem apletu.

Wykład 2 Składnia języka C# (cz. 1)

PROE wykład 3 klasa string, przeciążanie funkcji, operatory. dr inż. Jacek Naruniec

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

Platformy Programistyczne Podstawy języka Java

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

Podstawowe części projektu w Javie

Wykład 4: Klasy i Metody

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

JDK można pobrać ze strony

Programowane refleksyjne i serializacja

INFORMATYKA Studia Niestacjonarne Elektrotechnika

Wykład 6: Dziedziczenie

Wykład 6 Dziedziczenie cd., pliki

System obsªugi wej±cia i wyj±cia.

Kurs programowania. Wykład 2. Wojciech Macyna. 17 marca 2016

Programowanie współbieżne i rozproszone

Wykład 8: Obsługa Wyjątków

Kurs programowania. Wykład 3. Wojciech Macyna. 22 marca 2019

Programowanie rozproszone w języku Java

Wykład 8: klasy cz. 4

Pliki. Informacje ogólne. Obsługa plików w języku C

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

Programowanie 3 - Funkcje, pliki i klasy

Kurs programowania. Wstęp - wykład 0. Wojciech Macyna. 22 lutego 2016

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

Programowanie obiektowe

Transkrypt:

Apilkacje w środowisku Java - wykład 10 Strumienie 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

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. 2

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. 3

Klasy strumieniowe 4 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. 5

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. 6

Klasy strumieniowe - przykład 7 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 8 Ź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); 9

Klasy przedmiotowe przykład - try-with-resources 10 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 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()); 11

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). 12

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 13

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), 14

Klasy przetwarzające 15 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 16

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); 17

Buforowanie 18 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).

19 Strumienie binarne Klasy przetwarzające DataInputStream i DataOutputStream służą do odczytu/zapisu danych typów pierwotnych w postaci binarnej (oraz łańcuchów znakowych). Metody tych klas mają postać: typ readtyp() void writetyp(typ arg) gdzie typ odpowiada nazwie któregoś z typów pierwotnych. Mamy więc np. metody int readint(), double readdouble() itp. Dane typu String mogą być zapisywane/czytane do/z strumieni binarnych za pomocą metod writeutf i readutf. Przykład. Stwórzmy klasę Obserwacje, której obiekty reprezentują obserwacje. Każda obserwacaja ma: nazwę oraz odpowiadający jej ciąg (tablicę) liczb rzeczywistych. Może to być np. maxtemp z 12 liczbami, pokazującymi maksymalną temperaturę w 12 miesiącach roku. W klasie tej zdefiniujemy dwie metody służące do zapisu obserwacji w postaci binarnej do strumienia i odczytywania binarnych strumieni obserwacji. Format zapisu obserwacji w pliku binarnym: nazwa liczba_elementów_tablicy dane_tablicy

Strumienie binarne - przykład import java.io.*; class Obserwacje { String name; double[] data; public Obserwacje() { public Obserwacje(String nam, double[] dat) { name = nam; data = dat; public void writeto(dataoutputstream dout)throws IOException { dout.writeutf(name); dout.writeint(data.length); for (int i=0; i<data.length; i++) dout.writedouble(data[i]); public Obserwacje readfrom(datainputstream din)throws IOException { name = din.readutf(); int n = din.readint(); data = new double[n]; for (int i=0; i<n; i++) data[i] = din.readdouble(); return this; public void show() { System.out.println(name); for (int i=0; i<data.length; i++) System.out.print(data[i] + " "); System.out.println(""); 20

Strumienie binarne - przykład 21 import java.io.*; class BinDat { public static void main(string args[]) throws IOException { double[] a = {1, 2, 3, 4; double[] b = {7, 8, 9, 10; //tworzymy dwie obserwacje: Obserwacje obsa = new Obserwacje("Dane A", a); Obserwacje obsb = new Obserwacje("Dane B", b); obsa.show(); obsb.show(); try (DataOutputStream out = new DataOutputStream(new FileOutputStream("dane"))) { obsa.writeto(out); //zapis obserwacji do pliku obsb.writeto(out); //zapis obserwacji do pliku try (DataInputStream in = new DataInputStream(new FileInputStream("dane"))) { // z tego samego pliku odczytujemy dane do innych obiektów-obserwacji // i jednocześnie pokazujemy odczytane dane na konsoli new Obserwacje().readFrom(in).show(); new Obserwacje().readFrom(in).show();

Kodowanie Java posługuje się znakami w formacie Unicode. Są to - ogólnie - wielkości 16- bitowe. Środowiska natywne (np. Windows) najczęściej zapisują teksty jako sekwencje bajtów w różnych systemach kodowania (sposób kodowania nazywamy stroną kodową). W systemie Windows jest to najczęściej Cp1250 lub UTF-8. Powstaje zatem problem pogodzenia najczęściej bajtowego charakteru plików natywnych ze strumieniami znakowymi. Strumienie znakowe FileReader i FileWriter konwertują - niewidocznie dla nas - bajtowe źródła w znaki Unicodu i odwrotnie. Wykorzystywane są tu dwie klasy: InputStreamReader i OutputStreamWriter, które dokonują właściwych konwersji w trakcie czytania/pisania. Klasy te możemy wykorzystać również samodzielnie. Jeśli w konstruktorach tych klas nie podamy strony kodowej - przy konwersjach zostanie przyjęta domyślna strona kodowa. Aby się dowiedzieć, jakie jest domyślne kodowanie używamy metody String p = System.getProperty("file.encoding"); System.out.println(p); W zależności od ustawień na danej platformie otrzymamy różne wyniki. Np. ibm-852, Cp852 (Latin 2), Cp1252 (Windows Western Europe /Latin-1) albo UTF-8. Inna wersja konstruktorów pozwala na podanie stron kodowych, które będą używane do kodownia i dekodowania bajty-znaki. 22

Kodowanie - przykład Przykład. Napiszmy funkcję wykonującą konwersję strumienia wejściowego is o stronie kodowej incp do strumienia os o stronie kodowej outcp import java.io.*; import java.net.url; public class URLToFile { public static void convert(inputstream is, String incp, OutputStream os, String outcp) throws IOException { try (BufferedReader in = new BufferedReader(new InputStreamReader(is, incp)); BufferedWriter out = new BufferedWriter(new OutputStreamWriter(os, outcp))){ String line; while ((line = in.readline())!= null) { out.write(line); out.newline(); public static void main(string[] args) throws IOException { convert(new URL("http://www.kul.pl").openStream(), "UTF-8", new FileOutputStream("kul.txt"), "Cp1250"); 23

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? 24

Obiekty plikowe - klasa File 25 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.

Klasa Files 26 Strumienie we/wy są użyteczne, ale wiele operacji na plikach łatwiej jest wykonywać przy pomocy statycznych metod klasy Files z pakietu java.nio.file (To jest inna klasa niż File). Zapewnia ona ulepszoną reprezentację nowoczesnych systemów i obiektów plikowych (m.in. większą liczbę arybutów obiektów plikowych, obsługę linków symbolicznych) i w przeciwieństwie do klasy File dostarcza metod wejścia-wyjścia dla plików. Większość metod klasy Files ma argumenty typu Path, reprezentują one ścieżki obiektów plikowych (plików, katalogów) w sposób niezależny od konkretnego systemu plikowego. Ścieżki te uzyskujemy przy pomocy metody get z klasy Paths. Przykłady działania dla systemu Windows: Paths.get("C:/Temp/plik1.txt"); //absolutna ścieżka do pliku Paths.get("in1.txt"); Paths.get("."); //plik in1.txt z bieżącego katalogu //katalog bieżący Paths.get("../p2.txt"); //p2.txt z nadkatalogu bieżcego katalogu Paths.get("/"); Paths.get("/Temp"); Paths.get("Temp"); //główny katalog (root) bieżącego dysku //katalog Temp bieżącego dysku //podkatalog Temp bieżącego katalogu Paths.get("C:", "Temp", "p.txt"); //plik C:\Temp\p.txt

Klasa Files - metody KOPIOWANIE PLIKÓW. Metoda Files.copy(Path source, Path target, CopyOption... options) umożliwia kopiowanie plików z uwzględnieniem podanych opcji: REPLACE_EXISTING - zastąpienie pliku w przypadku, gdy docelowy plik istnieje (domyślnie wyjatek FileAlreadyExistsException) COPY_ATTRIBUTES - dla kopii pliku mają być zachowane atrybuty oryginału Metoda Files.move() - pozwala na zmianę nazwy lub umiejscowienia pliku. import java.nio.file.*; import static java.nio.file.standardcopyoption.*; public class FcopyDemo{ static void copyfile(string srcfn, String destfn, CopyOption... opt) throws IOException{ Files.copy(Paths.get(srcFn), Paths.get(destFn), opt); 27

Klasa Files - metody public static void main(string[] args)throws IOException{ copyfile("in1", "out2"); //wyjatek jeśli out2 istnieje copyfile("in1", "out1", REPLACE_EXISTING); //jeśli out1 istnieje to będzie zastąpiony copyfile("in1", "/Temp/in1",COPY_ATTRIBUTES); //kopiuje in1 do katalogu Temp z zachowaniem atrybutów PRZETWARZANIE WIERSZY PLIKU TEKSTOWEGO. Metoda static List<String> readalllines(path path, Charset cs) zwraca listę wszystkich wierszy pliku, wymagane jest podanie strony kodowej jako obiektu klasy Charset (domyślna strona kodowa Charset.defaultCharset()) for (String line: Files.readAllLines(Paths.get("in1"), System.out.println(line); Charset.defaultCharset()) ) 28

Klasa Files - metody CZYTANIE I ZAPISYWANIE BAJTÓW. Metoda Files.getAllBytes(Path) zwraca zawartość pliku jako tablicę bajtów. Tablicę możemy zapisać do pliku korzystając z metody klasy Files: static Path write(path path, byte[] bytes, OpenOption... options) Z metod tych korzystamy, gdy działamy na plikach binarnych, ale można ich użyć również do plików tekstowych. Dokonajmy zamiany znaków tabulacji na spację w pliku. Operacje readall...() jednokrotnie przetwarzają i od razu zamykają pliki (tak samo Files.write(...)). void tabtospace(string fname) throws IOException { Path fpath = Paths.get(fname); byte[] cont = Files.readAllBytes(fpath); for(int i=0; i<cont.lenght; i++){ if(cont[i]==0x09) cont[i] = (byte) ' '; //09 to hex kod znaku tabulacji Files.write(fpath, cont); 29

Klasa Files - metody CZYTANIE I ZAPIS WIERSZY. Druga wersja metody Files.write(): static Path write(path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options) ma jako argument listę wierszy, które mają być zapisane do pliku. Łatwo więc można zmienić kodowanie pliku. Do ustalania stron kodowych użyjemy statycznej metody forname z klasy Charset: Path file = Paths.get("page.html"); Charset cpin = Charset.forName("Cp1250"); cpout = Charset.forName("ISO8859-2"); Files.write(file, Files.readAllLines(file, cpin), cpout); Przedstawione metody readall... nadają się do operowania na stosunkowo niewielkich plikach, ponieważ wczytują do pamięci od razu całą zawartość pliku. Dla bardzo dużych plików powinniśmy użyć innych metod np. strumieni we/wy. Jeśli chcemy czytać plik sukcesywnie (i być może nie do końca), lepiej użyć Scannera. Dla skanera źródłem danych oprócz File, String, może być Path oraz dowolny Reader i InputStream. W konstruktorze Scannera możemy podać stronę kodową wczytywanego pliku (jego treść będzie dekodowana do Unicodu zgodnie z tą stroną). Scanner zamykamy za pomocą metody close() (nie zgłasza ona wyjątków kontrolowanych ) lub używamy try-with-resources. 30

Scanner 31 Klasa java.util.scanner pozwala na łatwy rozbiór informacji tekstowej zawierającej napisy i dane typów prostych. Możliwości: działa na klasie String, plikach (File), strumieniach, kanałach, np. Scanner sc = new Scanner(System.in); Scanner sc1 = new Scanner(new File("myNumbers")); String input = "1 fish 2 fish red fish blue fish"; Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*"); do parsowania używa wyrażeń regularnych (w tym prostych separatorów, ale również dowolnych złożonych wyrażeń), łatwo rozbija teksty na wiersze (String nextline(), boolean hasnextline()), umie wyróżnić i skonwertować dane typów prostych (a także BigDecimal), pozwala na rozbiór, polegający nie tylko na wyróżnianiu symboli rozdzielonych separatorami, ale również na wyróżnianiu symboli pasujących do podanego wyrażenia regularnego (metoda findintext(...), metoda skip(...)), sposób rozbioru można zmieniać w trakcie skanowania tekstu, m.in. stosując rozliczne metody next...(), w tym takie, które pozwalają podawać różne wyrażenia regularne. pozwala na zlokalizowany rozbiór danych.

Scanner 32 Wybrane metody: String next() - pobieranie kolejnych elementów (ang. token) (napisów rozdzielonych separatorem domyślnie białe znaki) boolean hasnext() - sprawdza czy jest dostępny kolejny element String nextline() - pobieranie kolejnych wierszy boolean hasnextline() - sprawdza czy jest kolejna linia int nextint() - pobieranie kolejnego elementu jako liczbę całkowitą boolean hasnextint() - sprawdzanie czy następny element jest liczbą całkowitą int nextdouble() - pobieranie kolejnego elementu jako liczbę rzeczywistą boolean hasnextdouble() - sprawdzanie czy następny element jest liczbą rzeczywistą Scanner usedelimiter(string regex) - ustawia separator skanera na separator skonstruowany na podstawie parametru

Skaner - przykład 33 import java.util.*; class Employee { String name; double salary; Employee(String n, double s) { name = n; salary = s; public double getsalary() { return salary; public String tostring() { return name + " " + salary;

Skaner - przykład public class Skaner1{ public static void main(string[] args) { String s1 = "1 2 3"; String s2 = "Jan Kowalski\t1200\nA. Grabowski\t1500"; Scanner scan1 = new Scanner(s1); int suma = 0; while (scan1.hasnextint()) suma += scan1.nextint(); System.out.println("Suma = " + suma); List<Employee> list = new ArrayList<>(); Scanner scan2 = new Scanner(s2); while (scan2.hasnextline()) { Scanner scan3 = new Scanner(scan2.nextLine()).useDelimiter("\\t"); String name = scan3.next(); double salary = scan3.nextdouble(); list.add(new Employee(name, salary)); double value = 0; for (Employee emp : list) { value += emp.getsalary(); System.out.println(emp); System.out.println("Suma zarobków: " + value); 34

Formatter 35 Klasa java.util.formatter zapewnia możliwości formatowania danych. Tworząc formator (za pomocą wywołania konstruktora) możemy określić: destynację formatowanych danych(dokąd mają być zapisane), którą może być: File, String, OutputStream, obiekty klas implementujących interfejs Appendable, czyli: BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder (szybsza wersja StringBuffer, bo niesynchronizowana), StringWriter, Writer lokalizację (ustawienia regionalne, reprezentowane przez obiekt klasy Locale), wpływającą m.in. na reprezentację liczb i dat, stronę kodową (do kodowania napisów) - dla strumieni, plików i stringów Uwaga: formatory dla destynacji implementujących interfejs Closeable (m.in. pliki, strumienie) powinny być po użyciu zamykane lub wymiatane (close(), flush()), co powoduje zamknięcie lub wymiecenie buforów tych destynacji.

Formatter 36 Formatowanie polega na wywołaniu jednej z dwóch wersji metody format (na rzecz formatora): Formatter format(locale l, String fmt, Object... args) Formatter format(string fmt, Object... args) Łańcuch formatu (parametr fmt) zawiera dowolne ciągi znaków oraz specjalne symbole formatujące. Dalej następują dane do wstawienia w łańcuch formatu w miejscu elementów formatu i do sformatowania według zasad określonych przez te elementy (zmienna liczba argumentów dowolnego typu formalnie Object). Dzięki autoboxingowi nie ma problemu z formatowaniem danych typów prostych. Dla uproszczenia dostępne są: statyczne metody format w klasie String, metody format i printf (działające tak samo) w klasach PrintStream i PrintWriter, wyprowadzajace sformatowane napisy na wyjście.

Formatter 37 Elementy formatu mają następującą ogólną postać %[arg_ind$][flags][width][.precision]conversion gdzie arg_ind$ numer argumentu (z listy argumentów args) do sformatowania przez dany element; numeracja zaczyna się od 1; poczynając od 2-go elementu można zastosować w tym miejscu znak <, co oznacza, że dany element ma być zastosowany wobec argumentu użytego w poprzednim formatowaniu flags znaki modyfikujące sposób formatowania (są różne dla różnych typów konwersji conversion) width minimalna liczba znaków dla danego argumentu w wynikowym napisie.precision liczba pokazywanych miejsc dziesiętnych (dla liczb rzeczywistych) lub maksymalna liczba wyprowadzonych znaków (dla np. napisów) conversion konwersja określa jak ma być traktowany i formatowany odpowiadający danemu elementowi argument, np. jako liczba rzeczywista, jako data, jako napis Uwaga: nawiasy kwadratowe oznaczają opcjonalność.

Formatter - konwersje 38 Wśród flag na szczególną uwagę zasługują: '-' wynik wyrównany w polu do lewej (domyślnie jest wyrównany do prawej), '+' wynik zawiera zawsze znak (dla typów liczbowych), ' ' wynik zawiera wiodącą spację dla argumentów nieujemnych (tylko dla typów liczbowych). Konwersja Może być stosowana wobec Wynik s lub S dowolnych danych Jeżeli argument jest null - napis "null": w przeciwym razie jeżeli klasa arg na to zezwala wynik wywołania arg.formatto(...) w przeciwnym razie wynik wywołania arg.tostring() Uwaga: użycie jako symbolu konwersji dużego S spowoduje zamianę liter napisu na duże. c lub C d f typów reprezentujących znaki Unicode typów reprezentujących liczby całkowite float, double, Float, Double, BigDecimal znak Unicode liczba całkowita (dziesiętna) liczba rzeczywista z separatorem miejsc dzisiętnych

Formatter - konwersje 39 Konwersja th Może być stosowana wobec Wynik godzina na zegarze 24-godzinnym-2 cyfry (00-23) tm minuty - 2 cyfry (00-59) ts sekundy - 2 cyfry (00-60) ty tm td danych reprezentujących czas, czyli: long, Long, Calendar, Date rok - 4 cyfry (np. 2014) miesiąc - 2 cyfry (01-12) dzień miesiąca - 2 cyfry (01-31) tr tt tf czas na zegarze 24 godzinnym sformatowany jako "%th:%tm" czas na zegarze 24 godzinnym sformatowany jako "%th:%tm:%ts" data sformatowana jako "%ty-%tm-%td"

Formatter - przykład Aby uzyskać sformatowane wyniki: liczbę z dwoma miejscami dziesiętnymi, datę w postaci rok-miesiąc-dzień możemy napisać: import java.util.*; public class Format1 { public static void main(string[] args) { double cena = 1.52; double ilosc = 3; double koszt = cena * ilosc; System.out.printf("Koszt wynosi %.2f zł", koszt); System.out.printf("\nData: %tf",calendar.getinstance()); Wynik: Koszt wynosi 4,56 zł Data: 2018-12-16 Warto tu zwrócić uwagę na to, że dla lokalizacji polskiej liczba pokazywana jest z przecinkiem jako separatorem miejsc dziesiętnych. Aby uzyskać kropkę można napisać: System.out.printf(Locale.ROOT, "Koszt wynosi %.2f zł", koszt); W tym przypadku stała statyczna Locale.ROOT oznacza neutralną lokalizację (bez wybranego kraju i języka). 40

Formatter - przykład 41 import java.util.calendar; public class Format2 { public static void main(string[] args) { System.out.println("Wyrównany wydruk tablicy (po 2 elementy w wierszu)"); int[] arr = { 1, 100, 200, 4000 ; int k = 1; for (int i : arr) { System.out.printf("%5d", i); if (k++%2 == 0) System.out.println(); // Wyrównany wydruk tablicy (po 2 elementy w wierszu) // 1 100 // 200 4000 // Zastosowanie znaku < //(element formatu stosowany wobec argumentu // z poprzedniego formatowania) System.out.println("Zaokrąglenia"); System.out.printf("%.3f %<.2f %<.1f", 1.256); //Zaokraglenia //1,256 1,26 1,3

Formatter - przykład 42 // Znak < szczególnie przydatny w datach/czasie Calendar c = Calendar.getInstance(); c.set(calendar.month, 1);// miesiace numerowane od 0 do 11 System.out.printf("\nW roku %ty i miesiącu %<tm mamy %d dni", c, c.getactualmaximum(calendar.date) ); //W roku 2018 i miesiącu 02 mamy 28 dni // Oczywiście możemy formatować do stringów String datenow = String.format("%td-%<tm-%<tY", System.currentTimeMillis()); System.out.printf("\n" + datenow); //16-12-2018 Formatowanie dat można też uzyskać za pomocą klas SimpleDateFormat oraz od Javy 8 : DateTimeFormatter. Umożliwiają one nie tylko formatowanie ale i parsowanie dat z napisów.

Serializacja obiektów Obiekty tworzone przez program rezydują w pamięci operacyjnej, w przestrzeni adresowej procesu. Są zatem nietrwałe, bo kiedy program kończy działanie wszystko co znajduje się w jego przestrzeni adresowej ulega wyczyszczeniu i nie może być odtworzone. Serializacja (szeregowanie) pozwala na utrwalanie obiektów. W Javie polega ona na zapisywaniu obiektów do strumienia. Podstawowe zastosowania serializacji: komunikacja pomiędzy obiektami/aplikacjami poprzez gniazdka (sockets), zachowanie obiektu (jego stanu i właściwości) do późniejszego odtworzenia i wykorzystania przez tę samą lub inną aplikację. Do zapisywania/odczytywania obiektów służą klasy ObjectOutputStream oraz ObjectInputStream, które należą do strumieniowych klas przetwarzających. Metoda klasy ObjectOutputStream: void writeobject(object o) zapisuje obiekt o do strumienia. Metoda klasy ObjectInputStream: Object readobject() odczytuje obiekt ze strumienia i zwraca referencję do niego Do strumieni mogą być zapisywane tylko serializowalne obiekty. Obiekt jest serializowalny jeśli jego klasa implementuje interfejs Serializable Prawie wszystkie klasy standardowych pakietów Javy implementują ten interfejs. Również tablice (które są obiektami specjalnych klas definiowanych w trakcie kompilacji) są serializowalne. 43

Serializacja obiektów - przykład Przykład: program zapisuje do strumienia obiekty - datę, tablicę opisów i odpowiadającą każdemu opisowi temperaturę. Następnie odczytuje te obiekty ze strumienia i odtwarza je. import java.io.*; import java.util.*; class Serial { public static void main(string args[]) { Date data = new Date(); int[] temperatura = { 25, 19, 22; String[] opis = { "dzień", "noc", "woda" ; // Zapis try { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("test.ser")); out.writeobject(data); out.writeobject(opis); out.writeobject(temperatura); out.close(); catch(ioexception exc) { exc.printstacktrace(); System.exit(1); 44

Serializacja obiektów - przykład 45 // Odtworzenie (zazwyczaj w innym programie) try { ObjectInputStream in = new ObjectInputStream( new FileInputStream("test.ser") ); Date odczytdata = (Date) in.readobject(); String[] odczytopis = (String[]) in.readobject(); int[] odczyttemp = (int[]) in.readobject(); in.close(); System.out.println(odczytData);//Sun Dec 11 21:19:34 CET 2017 //System.out.println(DateFormat.getInstance().format(odczytData)); //16.12.18 21:20 for (int i=0; i<odczytopis.length; i++) System.out.println(odczytOpis[i] + " " + odczyttemp[i]); catch(ioexception exc) { exc.printstacktrace(); System.exit(1); catch(classnotfoundexception exc) { System.out.println("Nie można odnaleźć klasy obiektu"); System.exit(1);

Serializacja obiektów 46 Metoda readobject() pobiera ze strumienia zapisane charakterystyki obiektu (w tym również oznaczenie klasy do której należy zapisany obiekt) - na ich podstawie tworzy nowy obiekt tej klasy i inicjuje go odczytanymi wartościami. Wynikiem jest referencja formalnego typu Object wskazująca na nowo utworzony obiekt, który jest identyczny z zapisanym. Ponieważ wynikiem jest Object, należy wykonać odpowiednią konwersję zawężającą do właściwego typu (referencji do konkretnej podklasy klasy Object, tej mianowicie, której egzemplarzem faktycznie jest odczytany obiekt). Może się też okazać, że w strumieniu zapisano obiekt klasy, która nie jest dostępna przy odczytywaniu (np. została usunięta). Wtedy przy tworzeniu obiektu z odczytanych danych powstanie wyjątek ClassNotFoundException, który musimy obsługiwać. Aby serializować obiekty własnych klas, klasa winna implementować interfejs Serializable. Interfejs ten jest pusty - nie musimy więc implementować żadnych jego metod, wystarczy tylko wskazać, że nasza klasa go implementuje. Takie interfejsy (bez metod) nazywane są interfejsami znacznikowymi. Ich jedyną funkcją jest umożliwienie sprawdzenia typu np. za pomocą operatora instanceof. Metoda writeobject to własnie robi, gdy podejmuje decyzje o zapisie: jeśli jej argument x jest typu Serializable (x instanceof Serializable ma wartośc true), to obiekt jest zapisywany do strumienia, w przeciwnym razie nie.

Serializacja obiektów 47 Przy serializacji nie są zapisywane pola statyczne oraz pola deklarowane ze specyfikatorem transient; specyfikatora transient używamy więc wobec elementów informacji o obiekcie, których nie chcemy poddawać utrwaleniu, np.: public transient int pole; Pełniejszą kontrolę nad sposobem serializacji możemy zyskać definiując odpowiednie metody w klasie obiektu serializowanego. Metody te winny mieć następujące sygnatury: private void readobject(java.io.objectinputstream stream) throws IOException, ClassNotFoundException; private void writeobject(java.io.objectoutputstream stream) throws IOException; Całkowitą kontrolę nad formatem i sposobem serializacji zyskujemy poprzez implementację w klasie interfejsu Externalizable i dostarczenie metod writeexternal i readexternal