Урок 19

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


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

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

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

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

Пример 1. Сериализация объекта

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.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeCat {
    public static void main(String[] args) {
        Cat c = new Cat("Барсик");
        try (FileOutputStream fs = new FileOutputStream("testSer.ser");
             ObjectOutputStream os = new ObjectOutputStream(fs)) {
            os.writeObject(c);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try (FileInputStream fis = new FileInputStream("testSer.ser");
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            c = (Cat) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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

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

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

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

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

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

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

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

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

Пример 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.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;
    }
}

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

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

Пример 3. Использование 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.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeBird {
    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("testSer.ser");
             ObjectOutputStream os = new ObjectOutputStream(fs)) {
            os.writeObject(bird);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try (FileInputStream fis = new FileInputStream("testSer.ser");
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            bird = (Bird) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Размер кольца после сериализации: "
                + bird.getRing().getSize());
    }
}

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

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

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



0 comments
Leave your comment: