CodeGym /Kursy /JAVA 25 SELF /Interfejs Serializable: podstawowe zasady

Interfejs Serializable: podstawowe zasady

JAVA 25 SELF
Poziom 42, Lekcja 1
Dostępny

1. Interfejs Serializable

Pamiętacie przykłady z poprzedniego wykładu? Klasy Javy, które chcemy serializować, muszą implementować specjalny interfejs — java.io.Serializable. To tzw. interfejs znacznikowy: nie zawiera żadnych metod, a po prostu „oznacza” klasę jako nadającą się do serializacji. Jeśli klasa implementuje ten interfejs, JVM pozwala serializować jej obiekty standardowymi środkami.

Nie warto serializować wszystkiego jak leci, ponieważ nie wszystkie obiekty można lub należy zapisywać do bajtów. Niektóre obiekty zależą od stanu systemu operacyjnego, otwartych plików lub połączeń sieciowych. Dlatego Java wymaga, aby wyraźnie oznaczyć klasę jako serializowalną.

Interfejs znacznikowy jest jak naklejka „dozwolone do pakowania” na pudełku. Jeśli takiej naklejki nie ma — pakowacz (JVM) odmawia pracy.

Przykład: deklaracja klasy serializowalnej

import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private int age;

    // Konstruktor, gettery i settery
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Dla estetyki: metoda toString()
    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

Zwróćcie uwagę:

  • Po prostu dodaliśmy implements Serializable do deklaracji klasy.
  • Nie trzeba implementować żadnych metod (interfejs jest pusty).
  • Wszystkie standardowe klasy Javy, które można serializować (np. ArrayList, HashMap, String), już implementują Serializable.

2. Jak uczynić własną klasę serializowalną

Zasada nr 1: po prostu dodaj implements Serializable

To wszystko, czego wymaga sama klasa. Ale są pewne niuanse!

Ważne: wszystkie obiekty zagnieżdżone również muszą być serializowalne.
Jeśli twoja klasa ma pola‑referencje do innych obiektów, one także muszą być serializowalne. Na przykład:

public class Profile implements Serializable {
    private User user; // User musi być serializowalny!
    private int level;
}

Jeśli choć jedno z pól nie jest serializowalne, przy próbie serializacji wystąpi wyjątek.

3. Przykład serializacji i deserializacji

Zobaczmy, jak zserializować i zdeserializować obiekt do pliku. Do tego służą klasy ObjectOutputStream i ObjectInputStream.

Przykład: serializacja obiektu User do pliku

import java.io.*;

public class SerializeDemo {
    public static void main(String[] args) {
        User user = new User("Alice", 30);

        // Zapisujemy obiekt do pliku
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("Obiekt pomyślnie zserializowany do pliku user.ser");
        } catch (IOException e) {
            System.out.println("Błąd serializacji: " + e.getMessage());
        }
    }
}

Co się tutaj dzieje?

  • Tworzymy obiekt User.
  • Otwieramy strumień ObjectOutputStream, który zapisuje do pliku "user.ser".
  • Wywołujemy writeObject(user). W tym momencie JVM przekształca obiekt w strumień bajtów i zapisuje go do pliku.

Przykład: deserializacja obiektu z pliku

import java.io.*;

public class DeserializeDemo {
    public static void main(String[] args) {
        // Odczytujemy obiekt z pliku
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
            User user = (User) ois.readObject();
            System.out.println("Obiekt został pomyślnie odtworzony: " + user);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("Błąd deserializacji: " + e.getMessage());
        }
    }
}

Co się tutaj dzieje?

  • Otwieramy strumień ObjectInputStream, czytający z pliku "user.ser".
  • Wywołujemy readObject(). JVM odtwarza obiekt z bajtów.
  • Nie zapomnij rzutować wynik do właściwego typu (User), ponieważ readObject() zwraca Object.
  • Może wystąpić ClassNotFoundException, jeśli klasa User nie zostanie znaleziona podczas deserializacji.

Wszystko razem: serializacja i deserializacja

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        User user = new User("Bob", 22);

        // Serializacja
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("Serializacja zakończona!");
        } catch (IOException e) {
            System.out.println("Błąd serializacji: " + e.getMessage());
        }

        // Deserializacja
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
            User loaded = (User) ois.readObject();
            System.out.println("Deserializacja zakończona! " + loaded);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("Błąd deserializacji: " + e.getMessage());
        }
    }
}

Wynik:

Serializacja zakończona!
Deserializacja zakończona! User{name='Bob', age=22}

4. Co dzieje się „pod maską” podczas serializacji

Gdy wywołujesz writeObject, JVM najpierw sprawdza, czy klasa implementuje interfejs Serializable. Jeśli klasa nie jest oznaczona jako serializowalna, zostaje zgłoszony wyjątek. Następnie JVM przechodzi po wszystkich zwykłych polach obiektu (czyli tych, które nie są static ani transient) i zapisuje ich wartości do strumienia bajtów. Jeśli wśród tych pól znajdują się inne obiekty, serializacja stosowana jest do nich rekurencyjnie, ale tylko wtedy, gdy również implementują Serializable.

Podczas deserializacji obiekt tworzony jest bez wywołania zwykłego konstruktora, a jego pola są wypełniane zapisanymi wartościami — jakby „konstruktor bez konstruktora” ożywiał obiekt ze strumienia bajtów.

Niektóre pola nie będą serializowane. Pola statyczne (static) należą do samej klasy, a nie do pojedynczego obiektu, dlatego ich wartości nie są zapisywane. Pola oznaczone jako transient również są pomijane — to wygodne dla danych tymczasowych, pamięci podręcznej lub poufnych informacji, takich jak hasła.

Schemat procesu serializacji

flowchart TB
    A[Obiekt User w pamięci] -- writeObject --> B[ObjectOutputStream]
    B -- zapisuje bajty --> C[Plik user.ser]
    C -- readObject --> D[ObjectInputStream]
    D -- odtwarza --> E[Obiekt User w pamięci]

5. Typowe błędy podczas używania Serializable

Błąd nr 1: pole‑referencja do nieserializowalnego obiektu. Jeśli w klasie User pojawi się pole typu, na przykład, Thread lub Socket, serializacja nie zadziała. Nie wszystkie obiekty da się zserializować — pamiętajcie o tym!

Błąd nr 2: nieserializowalne klasy zagnieżdżone. Jeśli klasa User zawiera klasę wewnętrzną, która nie jest static, serializacja może się nie powieść. Lepiej używać zagnieżdżonych klas static lub osobnych klas w oddzielnych plikach.

Błąd nr 3: próba serializacji pola static. Pola statyczne nie są serializowane — należą do klasy, a nie do obiektu. Po deserializacji static-pole będzie miało wartość określoną w klasie, a nie w zserializowanym obiekcie.

Błąd nr 4: niezgodność wersji klasy. Jeśli zmienisz strukturę klasy po serializacji (na przykład dodasz lub usuniesz pole), a następnie spróbujesz zdeserializować stary obiekt, może pojawić się błąd InvalidClassException. Do kontroli wersji służy specjalne pole serialVersionUID — o nim opowiemy szerzej w kolejnym wykładzie.

1
Zadanie
JAVA 25 SELF, poziom 42, lekcja 1
Niedostępne
Interfejs znacznikowy Serializable
Interfejs znacznikowy Serializable
1
Zadanie
JAVA 25 SELF, poziom 42, lekcja 1
Niedostępne
Prosta serializacja i deserializacja
Prosta serializacja i deserializacja
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION