środa, 8 grudnia 2010

Serializacja w Javie na potrzeby SCJP 6

Serializacja w Javie, a SCJP

W tym krótkim poście chciałbym opisać serializację w Javie na potrzeby egzaminu SCJP 6. Mówiąc najprościej serializacja to przekształcenie aktualnego stanu obiektu w strumień bajtów, który może być zapisany na dysku twardym. Odwrotny proces to deserializacja. Wspominajac o zachowaniu stanu obiektu mam namyśli oczywiście zachowanie wszystkich albo prawie wszystkich zmiennych instancji obiektu. Na początek kilka reguł:

Każdy obiekt, który ma być serializowany musi implementować interfejs Serializable.

Aby zapisać i odczytać stan obiektu korzystamy z klas: ObjectOutputStream i ObjectInputStream.

Prosty przykład na początek:

class Animal implements Serializable
{
public int age;

public Animal(int age)
{
this.age = age;
}

public String toString()
{
return "My age is: " + age;
}

public static void main(String[] args)
{
Animal a1 = new Animal(12);
Animal a2 = new Animal(2);

try
{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("my_animals.dat"));
oos.writeObject(a1);
oos.writeObject(a2);
oos.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

a1 = null;
a2 = null;

try
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("my_animals.dat"));
a1 = (Animal) ois.readObject();
a2 = (Animal) ois.readObject();
ois.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

System.out.println(a1.toString() + " " + a2.toString());
}

}



Do pliku zapisujemy stan dwóch obiektów klasy Animal, a następnie je odczytujemy. Trzeba oczywiście pamiętać o łapaniu wyjątków, w tym przykładzie nieelegancko łapię wszystkie wyjątki za jednym razem.



Na stan obiektu składają się: zmienne typu referencyjnego – wskazują obiekty w pamięci komputera, oraz zmienne przechowujące wartości typu prymitywnego np. int, char, long. Jeżeli dany obiekt posiada w sobie dowiązanie do innego obiektu np. Owner – Animal to klasa tego obiektu musi również implementować interfejs Serializable


class Animal implements Serializable
{
public int age;

public Animal(int age)
{
this.age = age;
}

public String toString()
{
return "My age is: " + age;
}
}

public class Owner implements Serializable
{
private String name;
private Animal animal;

public Owner(String name, Animal animal)
{
this.name = name;
this.animal = animal;
}
}


Sytuacja jest bajecznie prosta gdy sami piszemy każdą z klas. Co zrobić w przypadku, gdy korzystamy już z istniejącej klasy, która nie implementuje ważnego interfejsu? Z pomocą przychodzi słowo kluczowe transient. Pole klasy nim tak opatrzone nie jest serializowane. To oznacza, że ten obiekt lub wartość nie zostanie zapisana. Po deserializacji pole to będzie przyjmować null-a, gdy jest to zmienna referencyjna lub domyślną wartość dla danego typu np. 0 lub false. Warto zaznaczyć, że podczas desarializacji nie wykonuje się konstruktor klasy, do której dany obiekt należy, dlatego np. pole: cpuName będzie null-em.



public class Computer implements Serializable
{

private transient String cpuName = "CPU from China 66.6 MHz";

public String toString()
{
return "My CPU is: " + cpuName;
}

}


W przypadku próby zapisania obiektu, który nie jest serializowalny (nie implementuje Serializable) otrzymamy wyjątek: NotSerializableException.

Jeżeli bardzo nam zależy na zachowaniu stanu pól możemy zdefiniować odpowiednie metody, które pozwolą prócz samych obiektów na zachowanie innych wartości.



public class Home implements Serializable
{

private String name = "Home1";
private transient Window window = new Window(14, 34);

public String toString()
{
return "Window's size: " + window.height +" x "+ window.width;
}

private void writeObject(ObjectOutputStream oos) throws IOException
{
oos.defaultWriteObject();
oos.writeInt(window.width);
oos.writeInt(window.height);
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
{
ois.defaultReadObject();
window = new Window(ois.readInt(), ois.readInt());
}



public static void main(String[] args)
{
Home h = new Home();


try
{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("my_home.dat"));
oos.writeObject(h);
oos.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

h = null;


try
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("my_home.dat"));
h = (Home) ois.readObject();
ois.close();
}catch(Exception ex)
{
ex.printStackTrace();
}



}

}

class Window
{
public int width, height;

public Window(int w, int h)
{
width = w;
height = h;
}
}


Klasa Window nie jest serializowalna. Obiekty klasy Home posiadają pola, które są referencjami do obiektów klasy Window. Definiując metody writeObject i readObject możemy prócz obiektów zapisać również dwa int-y które są polami klasy Window. Przywrócenie odpowiednich wartości łączy się z odczytaniem wartości tych int-ów (w odpowiedniej kolejności) i wywołaniu konstruktora.


Co się dzieje w przypadku, gdy rozszerzamy daną klasę która nie implementuje Serializable?

Odpowiedź jest prosta: Jedynie te pola zostają serializowane, które należą do podklasy. Natomiast jakie wartości zostają nadane polom nad klasy po deserializacji?

Zanim odpowiem na to pytanie warto zaznaczyć, że w tym przypadku wykonują się konstruktory należące do nadklas. Pola będą przyjmować wartości nadane przez konstruktory zdefiniowane w nadklasach. Oto przykład:


public class Foo
{
String name = "Foo";
}

public class Bar extends Foo implements Serializable
{
String barName = "BarName";

public static void main(String[] args)
{
Bar bar = new Bar();
bar.name = "newFooName";

try{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("my_bar.dat"));
oos.writeObject(bar);
oos.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

bar = null;

try
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("my_bar.dat"));
bar = (Bar) ois.readObject();
ois.close();
}catch(Exception ex)
{
ex.printStackTrace();
}

System.out.println(bar.barName +" "+bar.name);
//Zostanie wypisany tekst: BarName Foo

}
}


Ostatnią rzeczą jaką warto powiedzieć to, że zapisywane są jedynie pola obiektów, a nie pola klasy. Więc wszystko co opatrzone słowem static nie zostanie zachowane.

Brak komentarzy:

Prześlij komentarz