Przygotował: Jacek Sroka 1 Programowanie obiektowe Wykład 9 Serializacja
Przygotował: Jacek Sroka 2 Przypomnienie Abstrakcja strumienia hierarchia binarna InputStream/OutputStream hierarchia znakowa Reader/Writer Wzorzec Dekorator Podstawowe typy strumienie skąd uzyskać strumienie do innych źródeł, np. gniazd sieciowych, zasobów sieci WWW Kodowanie znaków Podstawowe operacje na strumieniach ułatwienia Kompresja, szyfrowanie Standardowe wejście/wyjście NIO
Przygotował: Jacek Sroka 3 Zapisywanie całych obiektów Atrybutami obiektów mogą być inne obiekty Serializacj/deserializacja do postaci binarnej do Extensible Markup Language (XML) dokumenty XML zazwyczaj są zrozumiałe dla człowieka nawet jeżeli nie towarzyszy im stosowna dokumentacja, istnieją ogólnie przyjęte specyfikacje określających jak parsować i transformować dokumenty XML oraz wiele łatwo dostępnych narzędzi i bibliotek implementujących te specyfikacje, XML jest powszechnie stosowany do wymiany danych, zarówno w aplikacjach biurkowych, np. formaty zapisu dokumentów pakietów OpenOffice, jak i rozwiązaniach serwerowych, np. usługi Web Services Zastosowania lekka trwałość zachowanie danych między uruchomieniami aplikacji dane nie mieszczą się w pamięci zabezpieczenie przed utratą danych najlepiej stosować bazy danych (np. łatwe wyszukiwanie) programowanie rozproszone Remote Method Invocation (RMI), Enterprise Java Beans (EJB),...
Przygotował: Jacek Sroka 4 Serializacja w Javie ObjectOutputStream i ObjectInputStream void writeobject(object) Object readobject() interfejs java.io.serializable nie posiada żadnych metod znacznik implementowany przez większość klas ze standardowych bibliotek, np. klasy opakowujące i String Serializowany jest cały graf dostępnych obiektów domknięcie (ang. closure)
Przygotował: Jacek Sroka 5 Przykład import java.io.*; class Osoba implements Serializable { String nazwisko; String imię; Adres adreszameldowania; Osoba(String nazwisko, String imię, Adres adreszameldowania) { this.nazwisko = nazwisko; this.imię = imię; this.adreszameldowania = adreszameldowania; System.out.println("wywołanie konstruktora klasy Osoba"); public String tostring() { String adrpamięć = super.tostring(); return adrpamięć+"(" + nazwisko + ", " + imię + ", " + adreszameldowania + ")";
Przygotował: Jacek Sroka 6 Przykład c.d. class Adres implements Serializable { String miasto; String ulica; String nrdomu; String nrlokalu; Adres(String miasto, String ulica, String nrdomu, String nrlokalu) { this.miasto = miasto; this.ulica = ulica; this.nrdomu = nrdomu; this.nrlokalu = nrlokalu; System.out.println("wywołanie konstruktora klasy Adres"); public String tostring() { String adrpamięć = super.tostring(); return adrpamięć + "(" + miasto + ", " + ulica + ", " + nrdomu + ", " + nrlokalu + ")";
Przygotował: Jacek Sroka 7 Przykład c.d. public class TestSerializacji { public static void main(string[] args) throws Exception { Adres alternatywy4 = new Adres("Warszawa", "Alternatywy", "4", "9"); Osoba kotek = new Osoba("Kotek", "Zygmunt", alternatywy4); System.out.println(kotek); // wersja dla Linuxa String nazwapliku = "/tmp/lista.ser"; // wersja dla Windows //String nazwapliku = "c:\\lista.ser"; ObjectOutputStream out = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream(nazwaPliku))); out.writeobject("lista lokatorów"); out.writeobject(kotek); out.close(); ObjectInputStream in = new ObjectInputStream( new BufferedInputStream( new FileInputStream(nazwaPliku))); String nagłówek = (String) in.readobject(); kotek = (Osoba) in.readobject(); in.close(); System.out.println(kotek);
Przygotował: Jacek Sroka 8 Wynik Po odczytaniu dostaliśmy nowy obiekt o takiej samej zawartości Nie doszło do wykonania żadnych konstruktorów //wywołanie konstruktora klasy Adres //wywołanie konstruktora klasy Osoba //Osoba@1372a1a(Kotek, Zygmunt, Adres@ad3ba4(Warszawa, Alternatywy, 4, 9)) //Osoba@1c39a2d(Kotek, Zygmunt, Adres@bf2d5e(Warszawa, Alternatywy, 4, 9))
Przygotował: Jacek Sroka 9 Kilka referencji wskazujących ten sam obiekt Algorytm przechodzący domknięcie jest przygotowany na występowanie cykli nie dojdzie do powielenia współdzielonych atrybutów Własność ta jest zachowana również przy serializowaniu kilku grafów do tego samego strumienia po deserializacji grafy zachowają część wspólną
Przygotował: Jacek Sroka 10 Przykład Adres alternatywy4 = new Adres("Warszawa", "Alternatywy", "4", "9"); Osoba kotek = new Osoba("Kotek", "Zygmunt", alternatywy4); Osoba kołek= new Osoba("Kołek", "Zdzisław", alternatywy4); System.out.println(kotek); System.out.println(kołek); String nazwapliku = "/tmp/lista.ser"; ObjectOutputStream out = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream(nazwaPliku))); out.writeobject("lista lokatorów"); out.writeobject(kotek); out.writeobject(kołek); out.close(); ObjectInputStream in = new ObjectInputStream( new BufferedInputStream( new FileInputStream(nazwaPliku))); String nagłówek = (String) in.readobject(); kotek = (Osoba) in.readobject(); kołek = (Osoba) in.readobject(); in.close(); System.out.println(kotek); System.out.println(kołek);
Przygotował: Jacek Sroka 11 Wynik Po deserializacji oba adresy nie tylko mają tą samą zawartość ale są tym samym obiektem //wywołanie konstruktora klasy Adres //wywołanie konstruktora klasy Osoba //wywołanie konstruktora klasy Osoba //Osoba@1372a1a(Kotek, Zygmunt, Adres@ad3ba4(Warszawa, Alternatywy, 4, 9)) //Osoba@126b249(Kołek, Zdzisław, Adres@ad3ba4(Warszawa, Alternatywy, 4, 9)) //Osoba@df8ff1(Kotek, Zygmunt, Adres@1632c2d(Warszawa, Alternatywy, 4, 9)) //Osoba@1e97676(Kołek, Zdzisław, Adres@1632c2d(Warszawa, Alternatywy, 4, 9))
Przygotował: Jacek Sroka 12 Kontrolowanie serializacji Domyślnie serializacji podlegają wszystkie składowe, nawet prywatne Kiedy nie jest to wskazane dane poufne, np. hasła niektórych obiektów nie ma sensu serializować, np. strumieni i wątków Dodatkowo jeżeli przy próbie serializacji obiektów, które zawierają składowe nieimplementujące java.io.serializable zgłaszany jest wyjątek java.io.notserializableexception niestety do nieswojego kodu nie możemy dodać Serializable Można wskazać atrybuty, które mają być pomijane przez domyślny mechanizm serializacji zrezygnować z domyślnego mechanizmu (obsłużyć serializację/deserializację samemu)
Przygotował: Jacek Sroka 13 Przykład wskazanie atrybutów class Osoba implements Serializable { String nazwisko; String imię; transient Adres adreszameldowania; Osoba(String nazwisko, String imię, Adres adreszameldowania) { this.nazwisko = nazwisko; this.imię = imię; this.adreszameldowania = adreszameldowania; System.out.println("wywołanie konstruktora klasy Osoba"); public String tostring() { String adrpamięć = super.tostring(); return adrpamięć+"(" + nazwisko + ", " + imię + ", " + adreszameldowania + ")";
Przygotował: Jacek Sroka 14 Wynik Po deserializacji na składowych transient mamy wartość domyślną dla danego typu //wywołanie konstruktora klasy Adres //wywołanie konstruktora klasy Osoba //wywołanie konstruktora klasy Osoba //Osoba1@1372a1a(Kotek, Zygmunt, Adres@ad3ba4(Warszawa, Alternatywy, 4, 9)) //Osoba1@126b249(Kołek, Zdzisław, Adres@ad3ba4(Warszawa, Alternatywy, 4, 9)) //Osoba1@750159(Kotek, Zygmunt, null) //Osoba1@1abab88(Kołek, Zdzisław, null)
Przygotował: Jacek Sroka 15 Samodzielna obsługa serializacji Zamiast domyślnego mechanizmu serializacji/deserializacji mogą być użyte metody private void writeobject(objectoutputstream oos) throws IOException; private void readobject(objectinputstream ois) throws IOException, ClassNotFoundException Rozszerzenie domyślnego mechanizmu możemy uzyskać przez wykonanie na przekazanym jako parametr strumieni defaultwriteobject() oraz defaultreadobject() przydaje się do samodzielnego obsłużenia składowych nieimplementujących Serializable Oraz jak chcemy na standardowo serializowanych atrybutach wykonać jeszcze jakieś przekształcenia, np. kompresję/dekompresję lub kontrolę sumy kontrolnej
Przygotował: Jacek Sroka 16 Przykład class Osoba implements Serializable { String nazwisko; String imię; transient Adres adreszameldowania; private void writeobject(objectoutputstream oos) throws IOException { System.out.println("writeObject()"); oos.defaultwriteobject(); oos.writeobject(adreszameldowania); private void readobject(objectinputstream ois) throws... { System.out.println("readObject()"); ois.defaultreadobject(); adreszameldowania = (Adres) ois.readobject(); Osoba(String nazwisko, String imię, Adres adreszameldowania) { this.nazwisko = nazwisko; this.imię = imię; this.adreszameldowania = adreszameldowania; System.out.println("wywołanie konstruktora klasy Osoba"); public String tostring() {...
Przygotował: Jacek Sroka 17 Inny sposób zastąpienia domyślnego mechanizmu Klasa może implementować interfejs java.io.externalizable void writeexternal(objectoutput out) throws IOException void readexternal(objectinput in) throws IOException, ClassNotFoundException java.io.externalizable jest podinterfejsem java.io.serializable można przekazywać do metody writeobject() może występować w domknięciu serializowanego obiektu Ograniczenia Nie można wspomóc się domyślnym mechanizmem serializacji Dochodzi do wywołania domyślnego konstruktora (musi być i być publiczny) jak nie ma to java.io.invalidclassexception Zalety Można stosować w hierarchii klas (w poprzednim sposobie metody były prywatne) Pytanie: Jaką widoczność mają metody writeexternal()/readexternal()?
Przygotował: Jacek Sroka 18 Dostępność definicji klasy Deserializowana klasa musi być widoczna (w CLASSPATH) przez JVM, która dokonuje deserializacji wpp. java.lang.classnotfoundexception Nazwa serializowanej klasy zawsze ląduje w strumieniu niezależnie który mechanizm serializacji był użyty
Przygotował: Jacek Sroka 19 Przykład import java.io.*; class KlasaONiedostępnejDefinicji implements Serializable { public class TestWersjonowania { public static void main(string[] args) throws Exception { // wersja dla Linuxa String nazwapliku = "/tmp/test.ser"; // wersja dla Windows //String nazwapliku = "c:\\test.ser"; ObjectOutputStream oos = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream(nazwaPliku))); oos.writeobject(new KlasaONiedostępnejDefinicji()); oos.close();
Przygotował: Jacek Sroka 20 Przykład c.d. import java.io.*; public class PróbaOdczytu { public static void main(string[] args) throws Exception { ObjectInputStream ois = null; try { // wersja dla Linuxa String nazwapliku = "/tmp/test.ser"; // wersja dla Windows //String nazwapliku = "c:\\test.ser"; ois = new ObjectInputStream( new BufferedInputStream( new FileInputStream(nazwaPliku))); Object obj = ois.readobject(); catch (Exception e) { System.out.println(e); finally { try { ois.close(); catch (Exception e) { System.out.println("Nie udało się zamknąć strumienia");
Przygotował: Jacek Sroka 21 Wersjonowanie A co jeżeli definicja klasy się zmieniła? serializacja trwałą długo inna maszyna wirtualna ma inną wersję aplikacji Przy interfejsie Externalizable sami odpowiadamy obsługę wersjonowania Dla Serializable (ale nie Externalizable) wersjonowanie jest automatyczne Generowany jest unikatowy odcisk typu long na podstawie: nazwy klasy i użytych przy jej definicji modyfikatorów nazw wszystkich interfejsów implementowanych przez klasę opisu wszystkich metod i konstruktorów oprócz metod i konstruktorów prywatnych opisu wszystkich atrybutów poza atrybutami prywatnymi, statycznymi i oznaczonymi jako transient Jeśli identyfikatory nie pasują zgłaszany jest java.io.invalidclassexception Domyślne, ostrożne zachowanie można zmienić ustawiając static final long serialversionuid
Przygotował: Jacek Sroka 22 Przykład Serializowana klasa class KlasaOZmieniającejSięDefinicji implements Serializable { static final long serialversionuid = 2L; String staryatrybut = "jakaś wartość"; Definicja przy deserializacji class KlasaOZmieniającejSięDefinicji implements Serializable { static final long serialversionuid = 2L; String nowyatrybut = "deserializacja nie przypisze tej wartości"; Zapamiętana wartość usuniętego atrybutu zostanie zignorowana Nowy atrybut będzie miał wartość domyślną dla swojego typu Nie zostanie nawet wyliczone wyrażenie inicjalizujące Domyślną wartość UID można odczytać narzędziem serialver (wchodzi w skłąd JDK tak jak javac)