Сериализация

1. Что такое сериализация и десериализация

Сериализация - это процесс сохранения состояния объекта в последовательность байт.

Десериализация - это процесс восстановления объекта, из этих байт.

Java Serialization API предоставляет стандартный механизм для создания сериализуемых объектов. Процесс сериализации заключается в сериализации каждого поля объекта, но только в том случае, если это поле не имеет спецификатора static или transient. Поля, помеченные ими не могут быть предметом сериализации.

import java.io.Serializable;

public class Cat implements Serializable {
    private String name;

    public Cat(String name) {
        this.name = name;
    }
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeCat {
    private static final String FILE_NAME = "testSer.ser";

    public static void main(String[] args) {
        serialize();
        Cat cat = deserialize();
    }

    private static Cat deserialize() {
        Cat cat = null;
        try (FileInputStream fis = new FileInputStream(FILE_NAME);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            cat = (Cat) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
        return cat;
    }

    private static void serialize() {
        Cat cat = new Cat("Барсик");
        try (FileOutputStream fs = new FileOutputStream(FILE_NAME);
             ObjectOutputStream os = new ObjectOutputStream(fs)) {
            os.writeObject(cat);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

Для того, что бы объект мог быть сериализован, он должен реализовать интерфейс Serializable. Интерфейс java.io.Serializable не определяет никаких методов. Его присутствие только определяет, что объекты этого класса разрешено сериализовывать. При попытке сериализовать объект, не реализующий этот интерфейс, будет брошено java.io.NotSerializableException.

После того как объект был сериализован (превращен в последовательность байт), его можно восстановить, при этом восстановление можно проводить на любой машине (вне зависимости от того, где проводилась сериализация).

При десериализации поле со спецификатором transient получает значение по умолчанию, соответствующее его типу.

Поле со спецификатором static не изменяется.

Десериализация происходит следующим образом: под объект выделяется память, после чего его поля заполняются значениями из потока. КОНСТРУКТОР объекта при этом НЕ ВЫЗЫВАЕТСЯ.

Для работы по сериализации в java.io определены интерфейсы ObjectInput, ObjectOutput и реализующие их классы ObjectInputStream и ObjectOutputStream соответственно. Для сериализации объекта нужен выходной поток OutputStream, который следует передать при конструировании ObjectOutputStream. После чего вызовом метода writeObject() сериализовать объект и записать его в выходной поток.

2. Граф исходного объекта

Сериализуемый объект может хранить ссылки на другие объекты, которые в свою очередь так же могут хранить ссылки на другие объекты. И все ссылки тоже должны быть восстановлены при десериализации. Важно, что если несколько ссылок указывают на один и тот же объект, то в восстановленных объектах эти ссылки так же указывали на один и тот же объект. Чтобы сериализованный объект не был записан дважды, механизм сериализации некоторым образом для себя помечает, что объект уже записан в граф, и когда в очередной раз попадется ссылка на него, она будет указывать на уже сериализованный объект. Такой механизм необходим, что бы иметь возможность записывать связанные объекты, которые могут иметь перекрестные ссылки. В таких случаях необходимо отслеживать, был ли объект уже сериализован, то есть нужно ли его записывать или достаточно указать ссылку на него.

Если класс содержит в качестве полей другие объекты, то эти объекты так же будут сериализовываться и поэтому тоже должны быть сериализуемы. В свою очередь, сериализуемы должны быть и все объекты, содержащиеся в этих сериализуемых объектах и так далее. Полный путь ссылок объекта по всем объектным ссылкам, имеющимся у него и у всех объектов на которые у него имеются ссылки, и так далее - называется графом исходного объекта.

import java.io.Serializable;

public class Dog implements Serializable {
    private Collar theCollar;

    public Dog(Collar collar) {
        theCollar = collar;
    }

    public Collar getCollar() {
        return theCollar;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Dog dog = (Dog) o;

        return theCollar != null ? theCollar.equals(dog.theCollar) : dog.theCollar == null;

    }

    @Override
    public int hashCode() {
        return theCollar != null ? theCollar.hashCode() : 0;
    }
}
import java.io.Serializable;

public class Collar implements Serializable {
    private int collarSize;

    public Collar(int size) {
        collarSize = size;
    }

    public int getCollarSize() {
        return collarSize;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Collar collar = (Collar) o;

        return getCollarSize() == collar.getCollarSize();

    }

    @Override
    public int hashCode() {
        return getCollarSize();
    }
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeDog {

    private static final String FILE_NAME = "testSer.ser";

    public static void main(String[] args) {
        Collar collar = new Collar(3);
        Dog dog1 = new Dog(collar);
        Dog dog2 = new Dog(collar);
        serialize(dog1, dog2);
        Dog[] deserializedDogs = deserialize(2);

        System.out.println(dog1.equals(deserializedDogs[0]));
        System.out.println(dog2.equals(deserializedDogs[1]));
        Collar desCollar1 = deserializedDogs[0].getCollar();
        Collar desCollar2 = deserializedDogs[1].getCollar();
        System.out.println(desCollar1.equals(desCollar2));
    }

    private static void serialize(Dog... dogs) {
        try (FileOutputStream fs = new FileOutputStream(FILE_NAME);
             ObjectOutputStream os = new ObjectOutputStream(fs)) {
            for (Dog dog : dogs) {
                os.writeObject(dog);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    private static Dog[] deserialize(int dogNumber) {
        Dog[] dogs = new Dog[dogNumber];
        try (FileInputStream fs = new FileInputStream(FILE_NAME);
             ObjectInputStream os = new ObjectInputStream(fs)) {
            for (int i = 0; i < dogNumber; i++) {
                dogs[i] = (Dog) os.readObject();
            }
        } catch (IOException | ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
        return dogs;
    }
}

3. Ключевое слово transient

Если по какой-то причине класс не может реализовать интерфейс Serializable, переменная этого класса может быть объявлена как transient.

import java.io.Serializable;

public class Bird implements Serializable {
    private String name;
    private transient Ring ring;

    public Bird(String name, Ring ring) {
        this.name = name;
        this.ring = ring;
    }

    public Ring getRing() {
        return ring;
    }
}
public class Ring {
    private int size;

    public Ring(int size) {
        this.size = size;
    }

    public int getSize() {
        return size;
    }
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeBird {

    private static final String FILE_NAME = "testSer.ser";

    public static void main(String[] args) {
        Ring ring = new Ring(3);
        Bird bird = new Bird("pigeon", ring);
        System.out.println("Размер кольца перед сериализацией: "
                + bird.getRing().getSize());
        try (FileOutputStream fs = new FileOutputStream(FILE_NAME);
             ObjectOutputStream os = new ObjectOutputStream(fs)) {
            os.writeObject(bird);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
        try (FileInputStream fis = new FileInputStream("testSer.ser");
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            bird = (Bird) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
        System.out.println("Размер кольца после сериализации: "
                + bird.getRing().getSize());
    }
}

4. Десериализация и наследование

При десериализации производного класса, наследуемого от несериализуемого класса, вызывается конструктор без параметров родительского НЕ сериализуемого класса. И если такого конструктора не будет – при десериализации возникнет ошибка java.io.InvalidClassException. Конструктор же дочернего объекта, того, который мы десериализуем, не вызывается.

В процессе десериализации, поля НЕ сериализуемых классов (родительских классов, НЕ реализующих интерфейс Serializable) инициируются вызовом конструктора без параметров. Такой конструктор должен быть доступен из сериализуемого их подкласса. Поля сериализуемого класса будут восстановлены из потока.

import java.util.Objects;

public class Insect {
    private String name;

    public Insect(String type) {
        this.name = type;
    }

    public Insect() {
    }

    public String getName() {
        return name;
    }

    public void setName(String type) {
        this.name = type;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Insect insect = (Insect) o;
        return Objects.equals(name, insect.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }

    @Override
    public String toString() {
        return "Insect{" +
                "name='" + name + '\'' +
                '}';
    }
}
import java.io.Serializable;
import java.util.Objects;

public class Bug extends Insect implements Serializable {
    private boolean fly;

    public Bug(String type, boolean fly) {
        super(type);
        this.fly = fly;
    }

    public boolean isFly() {
        return fly;
    }

    public void setFly(boolean fly) {
        this.fly = fly;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        Bug bug = (Bug) o;
        return fly == bug.fly;
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), fly);
    }

    @Override
    public String toString() {
        return "Bug{" +
                "fly=" + fly +
                "} " + super.toString();
    }
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeInsect {
    private static final String FILE_NAME = "testSer.ser";

    public static void main(String[] args) {
        Bug bug = new Bug("Майский жук", true);
        System.out.println("До сериализации " + bug);
        try (FileOutputStream fs = new FileOutputStream(FILE_NAME);
             ObjectOutputStream os = new ObjectOutputStream(fs)) {
            os.writeObject(bug);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
        try (FileInputStream fis = new FileInputStream(FILE_NAME);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            bug = (Bug) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
        System.out.println("После сериализации " + bug);
    }
}
Read also:
Trustpilot
Trustpilot
Comments