Рубрики
Без рубрики

Что такое serialVersionUID в Java?

В этом уроке мы объясним, что такое serialVersionUID в Java, и рассмотрим пример сериализации и десериализации с его использованием.

Автор оригинала: Luka Čupić.

Вступление

В этой статье мы обсудим концепцию, связанную с сериализацией и десериализацией в Java. Хотя иногда это рассматривается как ” часть черной магии API сериализации Java “, в этой статье мы увидим, что serialVersionUID на самом деле довольно прост и понятен.

Во-первых, мы остановимся на сериализации и десериализации, чтобы вспомнить некоторые важные идеи, которые нам понадобятся позже. После этого мы глубже погрузимся в serialVersionUID и покажем, что это такое и как это работает.

Наконец, в заключение мы покажем пример, который должен связать все воедино.

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

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

Десериализация-это процесс, противоположный сериализации. Он принимает представление потока байтов объекта (например, из файла или сокета) и преобразует его обратно в объект Java, который находится внутри JVM.

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

Без класса, реализующего этот интерфейс, невозможно ни сериализовать, ни десериализовать объекты из этого класса. Выражаясь словами Сериализуемого Javadoc :

“Сериализуемость класса обеспечивается классом, реализующим интерфейс java.io.Serializable*.

Что такое serialVersionUID?

Для правильной работы сериализации и десериализации каждый сериализуемый класс должен иметь связанный с ним номер версии – serialVersionUID . Цель этого значения-убедиться, что классы, используемые отправителем (сериализуемым) и получателем (десериализуемым) сериализованного объекта, совместимы друг с другом.

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

При чтении объекта (т. е. десериализации) читатель может загрузить “новый” объект в “старое” представление. В лучшем случае это может иметь неприятные последствия, а в худшем-полное нарушение бизнес-логики.

Именно по этой причине serialVersionUID существует и обычно используется с всеми сериализуемыми объектами. Он используется для проверки того, что обе “версии” объекта (на стороне отправителя и получателя) совместимы, т. е. идентичны.

В случае, если действительно необходимо обновить класс, это может быть обозначено увеличением значения serialVersionUID . Таким образом, сериализованная версия будет иметь обновленное РУКОВОДСТВО, которое будет сохранено вместе с объектом и доставлено читателю.

Если у читателя нет новейшей версии класса, будет выдано исключение InvalidClassException .

Как сгенерировать serialVersionUID?

Согласно документации, каждое поле serialVersionUID должно быть статическим , окончательным и иметь тип длинный . Модификатор доступа может быть произвольным, но настоятельно рекомендуется, чтобы во всех объявлениях использовался модификатор private .

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

private static final long serialVersionUID = 42L;

Ранее мы упоминали, что все сериализуемые классы должны реализовывать интерфейс Сериализуемый .

Этот интерфейс предполагает , что все сериализуемые классы могут объявлять serialVersionUID , но не обязаны это делать. В случае, если класс не имеет явно объявленного значения serialVersionUID, оно будет сгенерировано средой выполнения сериализации.

Однако настоятельно рекомендуется , чтобы все сериализуемые классы явно объявляли значение serialVersionUID .

Это связано с тем, что по умолчанию вычисление serialVersionUID является сложным и, следовательно, чувствительным к очень небольшим различиям в средах. Если в процессе сериализации-десериализации используются два разных компилятора, во время десериализации может возникнуть исключение InvalidClassException , поскольку классы, по-видимому, не будут совпадать, даже если они содержат одно и то же содержимое, дословно.

Наконец, если в классе присутствуют какие-либо переходные или статические поля, они будут игнорироваться во время процесса сериализации и будут нулевыми после десериализации.

Пример serialVersionUID

Давайте определим класс, который мы будем использовать для сериализации и десериализации. Конечно, он будет реализовывать Сериализуемый интерфейс, и мы начнем с serialVersionUID , являющегося 1 :

public class Spaceship implements Serializable {

    private static final long serialVersionUID = 1L;

    private Pilot pilot;
    private Engine engine;
    private Hyperdrive hyperdrive;

    public void fly() {
        System.out.println("We're about to fly high among the stars!");
    }

    // Constructor, Getters, Setters
}

Затем мы реализуем метод serializeObject () , который будет отвечать за сериализацию объекта и запись его в файл .ser :

public void serializeObject(Spaceship spaceship) {
    ObjectOutputStream out = new ObjectOutputStream(
        new FileOutputStream("./spaceship.ser")
    );

    out.writeObject(spaceship);
    out.close();
}

Наш метод сериализует объект spaceship в файл .ser через Поток вывода файлов . Этот файл теперь содержит сериализованное содержимое нашего объекта.

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

Теперь давайте реализуем метод DeserializeObject () , который принимает этот файл .ser и создает из него объект:

public void deserializeObject(String filepath) {
    Spaceship ship;

    ObjectInputStream in = new ObjectInputStream(
        new FileInputStream(filepath)
    );
        
    ship = (Spaceship) in.readObject();
    in.close();

    ship.fly();
}

Давайте вызовем этих двоих и понаблюдаем за выводом:

public class Main {
    public static void main(String[] args) {
        Spaceship spaceship = new Spaceship();
        serializeObject(spaceship);
        deserializeObject("./spaceship.ser");
    }
}

Это приведет к:

We're about to fly high among the stars!

Наш метод DeserializeObject() загрузил сериализованный файл в JVM и успешно преобразовал его в Космический корабль объект.

Чтобы продемонстрировать проблему, упомянутую ранее в отношении управления версиями, давайте изменим значение serialVersionUID с 1L на 2L в нашем Космическом корабле классе.

После этого давайте изменим наш метод main () , чтобы снова прочитать файл, не записывая его с измененным идентификатором serialVersionUID :

public class Main {
    public static void main(String[] args) {
        deserializeObject("./spaceship.ser");
    }
}

Конечно, это приведет к:

Exception in thread "main" java.io.InvalidClassException ...

Как и ожидалось, причина исключения кроется в serialVersionUID .

Поскольку мы не записали новые данные после обновления значения serialVersionUID до 2L , сериализованный объект по-прежнему содержит 1L в качестве своего serialVersionUID .

Однако метод DeserializeObject() ожидал, что это значение будет 2L , потому что это фактическое новое значение из экземпляра Spaceship . Из-за этого несоответствия между сохраненным и восстановленным состоянием объекта Spaceship было соответствующим образом вызвано исключение.

Вывод

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

Надеюсь, теперь должно быть немного более ясно, в чем его назначение и как его правильно использовать в будущих проектах.