1. Wprowadzenie
W Javie serializacja działa tylko dla tych obiektów, które wyraźnie na to zezwalają. W tym celu klasa musi implementować specjalny interfejs — java.io.Serializable.
import java.io.Serializable;
public class Person implements Serializable {
// Pola, konstruktory, metody
}
Serializable — to interfejs znacznikowy: nie ma metod, po prostu informuje JVM — „tę klasę można serializować, bez obaw!”. Jeśli spróbujesz zserializować obiekt klasy, która nie implementuje Serializable, otrzymasz wyjątek NotSerializableException. Nawet jeśli choć jedno pole (lub obiekt zagnieżdżony) nie jest serializowalne — serializacja się nie powiedzie.
ObjectOutputStream i ObjectInputStream
- ObjectOutputStream — klasa, która zapisuje obiekty do strumienia (np. do pliku lub przez sieć).
- ObjectInputStream — klasa, która odczytuje obiekty ze strumienia.
Działają w parze: jeden serializuje obiekt, drugi — deserializuje.
Podstawowe metody
- writeObject(Object obj) — serializuje obiekt i zapisuje go do strumienia.
- readObject() — odczytuje obiekt ze strumienia, deserializuje i zwraca go.
Ważne: oba klasy działają na wierzchu zwykłych strumieni wejścia-wyjścia (OutputStream i InputStream). Najczęściej używa się ich wraz ze strumieniami plikowymi — FileOutputStream i FileInputStream, ale można je stosować także ze strumieniami sieciowymi.
2. Przykład serializacji
Napiszmy prosty przykład: zserializujemy i zdeserializujemy obiekt klasy Person.
Krok 1. Opis klasy
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// pole transient nie będzie serializowane
private transient String secret;
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", secret='" + secret + "'}";
}
}
Krok 2. Serializacja obiektu do pliku
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeDemo {
public static void main(String[] args) throws Exception {
Person person = new Person("Alice", 30, "likes pizza");
// Tworzymy strumień do zapisu do pliku
FileOutputStream fileOut = new FileOutputStream("person.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Zapisujemy obiekt
out.writeObject(person);
// Zamykamy strumienie
out.close();
fileOut.close();
System.out.println("Obiekt zserializowano do pliku person.bin");
}
}
Krok 3. Deserializacja obiektu z pliku
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeDemo {
public static void main(String[] args) throws Exception {
// Otwieramy strumień do odczytu z pliku
FileInputStream fileIn = new FileInputStream("person.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// Odtwarzamy obiekt
Person person = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("Obiekt zdeserializowany: " + person);
}
}
Oczekiwany wynik
Obiekt zserializowano do pliku person.bin
Obiekt zdeserializowany: Person{name='Alice', age=30, secret='null'}
Uwaga! Pole transient nie jest serializowane. Po deserializacji będzie miało wartość null. Jest to ważne dla danych tymczasowych lub wrażliwych.
3. Ograniczenia i szczegóły
Wszystkie pola muszą być serializowalne
Jeśli klasa ma pola, które same nie implementują Serializable (lub zawierają takie obiekty), serializacja zakończy się błędem. Na przykład pól typu Thread lub Socket nie da się tak po prostu uczynić serializowalnymi.
Pola statyczne i transient
- Pola statyczne (static) nie są serializowane: należą do klasy, a nie do konkretnego obiektu.
- Pola transient — oznaczone jako transient są jawnie wyłączane z serializacji i po odtworzeniu otrzymują wartości domyślne (null, 0 itd.).
Wyjątki
- Próba zserializowania obiektu, który nie implementuje Serializable, powoduje NotSerializableException.
- Podczas deserializacji możliwe są błędy: nie znaleziono pliku, niedopasowanie klas, uszkodzone dane itd.
Wersje klas
Jeśli zmienisz strukturę klasy po serializacji (np. dodasz/usuniesz pola), podczas deserializacji może wystąpić InvalidClassException. Do kontroli wersji używa się specjalnego pola serialVersionUID (więcej o nim w jednej z kolejnych lekcji).
4. Praktyka: serializacja i deserializacja obiektu do pliku
Załóżmy, że mamy klasę Person i chcemy zapisać listę osób do pliku oraz odczytać ją z powrotem.
Klasa Person (serializowalna)
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private transient String secret; // nie będzie serializowane
public Person(String name, int age, String secret) {
this.name = name;
this.age = age;
this.secret = secret;
}
@Override
public String toString() {
return name + " (" + age + "), secret: " + secret;
}
}
Serializacja listy osób
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SerializeListDemo {
public static void main(String[] args) throws Exception {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30, "likes pizza"));
people.add(new Person("Bob", 25, "hates broccoli"));
FileOutputStream fileOut = new FileOutputStream("people.bin");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Zapisujemy listę
out.writeObject(people);
out.close();
fileOut.close();
System.out.println("Lista osób została zserializowana.");
}
}
Deserializacja listy osób
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class DeserializeListDemo {
public static void main(String[] args) throws Exception {
FileInputStream fileIn = new FileInputStream("people.bin");
ObjectInputStream in = new ObjectInputStream(fileIn);
// Odtwarzamy listę
List<Person> people = (List<Person>) in.readObject();
in.close();
fileIn.close();
for (Person p : people) {
System.out.println(p);
}
}
}
Wynik:
Alice (30), secret: null
Bob (25), secret: null
5. Typowe błędy
Błąd nr 1: Klasa nie implementuje Serializable. Jeśli zapomnisz dodać implements Serializable, przy próbie serializacji otrzymasz NotSerializableException. To najczęstszy i najprostszy błąd.
Błąd nr 2: Nieserializowalne pole. Jeśli obiekt zawiera pole, które nie jest serializowalne (np. Thread, Socket lub inny typ bez Serializable), serializacja się nie powiedzie. Oznacz takie pola jako transient lub spraw, by były serializowalne.
Błąd nr 3: Zmiana struktury klasy. Jeśli obiekt został zserializowany, a później klasa została zmieniona (dodano/usunięto pola), przy odczycie możliwy jest InvalidClassException. Ustal serialVersionUID dla stabilnej kontroli wersji.
Błąd nr 4: Próba serializacji pól statycznych. Pola z modyfikatorem static nie są serializowane. Po deserializacji ich wartości będą takie, jak wynikające z definicji klasy, a nie te, które były podczas serializacji.
Błąd nr 5: Niezamknięte strumienie. Jeśli nie zamykasz strumieni po zakończeniu pracy, możesz otrzymać uszkodzony plik lub wyciek zasobów. Używaj try-with-resources albo zamykaj strumienie jawnie.
Błąd nr 6: Niedopasowanie klas. Jeśli klasa została przemianowana lub przeniesiona do innego pakietu, deserializacja się nie powiedzie — wymagane jest dokładne dopasowanie nazwy i pakietu klasy zapisanej w strumieniu.
GO TO FULL VERSION