Latest Entries »

Wednesday, August 5, 2009

Zapisanie, serializacja obiektów Java


Środowisko programistyczne java pozwala na kilka sposobów zapisania obiektu do "źródła", które po zamknięciu programu będzie przechowywać wartości obiektów. Do głowy przychodzą mi dwa pomysły

  • łatwy - zapisać stan obiektu w pliku tekstowym zapisując każde pole

  • łatwiejszy - wykorzystać mechanizm serializacji obiektu.


  • Mając na myśli łatwiejszy rozważam tutaj korzyści z serializacji (szybkości implementacji oraz elastyczności programowania).

    Serializacja obiektów w java jest swego rodzaju zrzutem obiektu z pamięci RAM to strumienia bajtowego zapisanego na np dysku twardym. Do tego rodzaju operacji są wykorzystywane klasy strumieniowe : ObjectOutputStream, ObjectInputStream oraz odpowiednie metody void writeObject(Object o), Object readObject(). Standardowo klasy, które są dostępne w javie od razu posiadają metody do zapisu/odczytu wartości danego typu oraz co ważne: implementują intefrejs Serializable np:
    writeUTF(String s) / readUTF()

    writeFloat(float f) / readFloat()
    writeDobule(double d) / read Double()

    etc...

    Zatem jak zapisać/odczytać ?



    Podrzucam spakowany projekt NetBeans SerializationExample.

    Najpierw trzeba utworzyć obiekt ObjectOutputStream aby móc w nim zapisać dane, które nas interesują.



    // Metoda do zapisu danych w pliku test.temp
    try
    {
    ObjectOutputStream MyObjectOutputStream = new ObjectOutputStream(new FileOutputStream("test.temp"));
    MyObjectOutputStream.writeUTF(serializujTextField.getText());
    MyObjectOutputStream.close();
    }
    catch (IOException ex)
    {
    ex.printStackTrace();
    }


    Następnie gdy zapisaliśmy w pliku test.temp spróbujmy zamknąć program, a po uruchomieniu wcisnąć przycisk odczytaj.
    Udostępnię teraz kod, który odczytuje z pliku test.temp naszą zapisaną wartość w String.


    try {
    ObjectInputStream OIS = new ObjectInputStream(new FileInputStream("test.temp"));
    serializujLabel.setText(OIS.readUTF());
    OIS.close();
    }
    catch (IOException ex)
    {

    }


    Działa !

    Jednak dla bardziej złożonych problemów nie wystarczy zapisać samego pola obiektu (String), a trzeba zapisać cały obiekt własnej klasy wraz z jego wszystkimi polami typów prostych (int,char,boolean,float,itp) oraz pola referencyjne (MojaJakasKlasa,JFrame,JButton, itp itp). Zatem stwórzmy klasę o nazwie Osoba, która będzie przechowywać dwa pola imię, nazwisko.

    Przykładowy kod klasy z setterami, getterami i konstruktorem:



    public class Osoba{

    private String imie;
    private String nazwisko;

    public String getImie() {
    return imie;
    }

    public void setImie(String imie) {
    this.imie = imie;
    }

    public String getNazwisko() {
    return nazwisko;
    }

    public void setNazwisko(String nazwisko) {
    this.nazwisko = nazwisko;
    }


    public Osoba(String imie, String nazwisko) {
    this.imie = imie;
    this.nazwisko = nazwisko;
    }
    }


    Skoro dodałem już klasę Osoba to niech obiekt tej klasy znajdzie się w głównej formie MyJFrame i zostanie utworzony jeśli naciśniemy przycisk serializuj w panelu Serializacja obiektu Osoba. Następnie zamknijmy program i włączmy go ponownie i kliknijmy odczytaj. Widać, że nie możemy odczytać z ObjectInputStream ponieważ nasza klasa nie implementuje intefrejsu Serializable.
    Powstanie wyjątek typu:

    java.io.InvalidClassException: serializationexample.Osoba; serializationexample.Osoba; class invalid for deserialization


    Zatem musimy zmienić nagłówek klasy na następujący:


    public class Osoba implements Serializable
    .
    Zapiszmy zmiany w projekcie, utwórzmy obiekt osoba wpisując imię i nazwisko w polach tekstowych, kliknijmy serializuj, następnie zamknijmy, otwórzmy program i wciśnijmy przycisk odczytaj. Widać, że program już działa jak powinien.
    Jedyną znaczną różnicą wobec przykładu z typem String jest to, że musimy dokonać konwersji zawężającej do typu Osoba:




    o = (Osoba) MyObjectInputStreamOsoba.readObject();
    MyObjectInputStreamOsoba.close();
    imieLabel.setText(o.getImie());
    nazwiskoLabel.setText(o.getNazwisko());




    Dodam na koniec ciekawostkę, że intefrejs Serializable nie posiada żadnych metod do przedefiniowania i nazywa się znacznikowym intefrejsem. Potrzeba istnienia takich intefrejsów polega na tym, że wyznaczają typ naszej klasy:

    if (O instance of Serializable)

    Powyższa linijka kodu zwróci wartość true, a kompilator będzie wiedział, że obiekty można zapisywać/odczywać.

    Jeżeli mamy rozbudowaną hierarchię dziedziczenia oraz klasa Osoba posiada inne pola obiektowe np. Adres a, HistoriaChoroby h, to nie musimy się martwić o zapisanie tych pól albowiem java rekurencyjnie zapisuje wszystkie pola klasy (muszą oczywiście implementować interfejs Serializable).

    Jeśli mamy bardzo rozbudowaną klasę, która używa mechanizmu szeregowania(serializacji), a nie podoba się nam, że wszystkie pola są zapisywane możemy w prosty sposób powiedzieć kompilatorowi aby nie zapisywał danego pola klasy. Do tego celu musimy użyć słowa kluczowego javy transient przykład:


    private transient String maloWaznaZmienna;

    Orginalny post z mojej domeny rpiesnikowski.pl

    Dziękuję za uwagę i pozdrawiam
    Robert.

0 comments:

Post a Comment