Что такое serialVersionUID и почему я должен его использовать?

3000

Eclipse выдает предупреждения, когда serialVersionUIDотсутствует.

Сериализуемый класс Foo не объявляет статическое окончательное поле serialVersionUID типа long

Что это такое serialVersionUIDи почему это важно? Пожалуйста, покажите пример, где отсутствие serialVersionUIDприведет к проблеме.

ashokgelal
источник
1
Найдите хорошую практику о serialversionUID; dzone.com/articles/what-is-serialversionuid
ceyun

Ответы:

2290

Документы для java.io.Serializable, вероятно, примерно так же хорошо, как объяснение:

Среда выполнения сериализации связывает с каждым сериализуемым классом номер версии, называемый a serialVersionUID, который используется во время десериализации для проверки того, что отправитель и получатель сериализованного объекта загрузили классы для этого объекта, которые совместимы в отношении сериализации. Если получатель загрузил класс для объекта, который отличается serialVersionUIDот класса соответствующего отправителя, то десериализация приведет к InvalidClassException. Сериализуемый класс может объявить свой собственный serialVersionUIDявно, объявив поле с именем, serialVersionUIDкоторое должно быть статическим, конечным и типа long:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

Если сериализуемый класс явно не объявляет a serialVersionUID, то среда выполнения сериализации вычислит serialVersionUIDзначение по умолчанию для этого класса на основе различных аспектов класса, как описано в Спецификации сериализации объектов Java (TM). Однако настоятельно рекомендуется, чтобы все сериализуемые классы явно объявляли serialVersionUIDзначения, поскольку serialVersionUIDвычисления по умолчанию очень чувствительны к деталям классов, которые могут различаться в зависимости от реализаций компилятора и, следовательно, могут привести к непредвиденным InvalidClassExceptionsпоследствиям при десериализации. Поэтому, чтобы гарантировать согласованное serialVersionUIDзначение в разных реализациях Java-компилятора, сериализуемый класс должен объявить явное serialVersionUIDзначение. Также настоятельно рекомендуется, чтобы явныйserialVersionUIDобъявления, где это возможно, используют модификатор private, поскольку такие объявления применяются только к немедленно объявленным serialVersionUIDполям класса и не являются полезными в качестве унаследованных членов.

Джон Скит
источник
326
Итак, по сути, вы говорите, что, если пользователь не понимает весь вышеупомянутый материал, указанный пользователь не должен беспокоиться о сериализации? Я полагаю, вы ответили "как?" вместо того, чтобы объяснять «почему?». Я, например, не понимаю, почему я должен беспокоиться о SerializableVersionUID.
Зигги
366
Причина находится во втором абзаце: если вы не укажете явным образом serialVersionUID, значение будет сгенерировано автоматически - но это хрупко, потому что это зависит от реализации компилятора.
Джон Скит
14
И почему Eclipse говорит, что мне нужно "private static final long serialVersionUID = 1L;" когда я расширяю класс Exception?
ДжонМерлино
21
@JohnMerlino: Ну, я бы не ожидал, что он вам понадобится, но может предложить его , чтобы правильно сериализовать исключения. Если вы не собираетесь их сериализовать, вам действительно не нужна константа.
Джон Скит
16
@JohnMerlino, чтобы ответить на вопрос «почему»: Exception реализует Serializable, и eclipse предупреждает, что вы не установили serialVersionUID, что было бы хорошей идеей (если вы не хотите сериализовать класс), чтобы избежать проблем, которые публикует JonSkeet контуры.
zpon
475

Если вы сериализуете только потому, что вам нужно сериализовать ради реализации (кого волнует, если вы сериализуете для HTTPSession, например ... если он сохранен или нет, вы, вероятно, не заботитесь об de-serializingобъекте формы), тогда вы можете игнорируй это.

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

В большинстве случаев вы, вероятно, не будете использовать сериализацию напрямую. Если это так, сгенерируйте значение SerialVersionUIDпо умолчанию , щелкнув опцию быстрого исправления, и не беспокойтесь об этом.

MetroidFan2002
источник
78
Я бы сказал, что если вы не используете сериализацию для постоянного хранения, вы должны использовать @SuppressWarnings, а не добавлять значение. Он меньше загромождает класс и сохраняет работоспособность механизма serialVersionUID, чтобы защитить вас от несовместимых изменений.
Том Андерсон
25
Я не вижу, как добавление одной строки (аннотация @SuppressWarnings) в отличие от другой строки (сериализуемого идентификатора) «меньше загромождает класс». И если вы не используете сериализацию для постоянного хранения, почему бы вам просто не использовать «1»? В любом случае вас не заботит автоматически созданный идентификатор.
MetroidFan2002
71
@ MetroidFan2002: Я думаю, что @ TomAnderson serialVersionUIDзащищает вас от несовместимых изменений. Использование @SuppressWarningsдокументов лучше, если вы не хотите использовать класс для постоянного хранения.
AbdullahC
11
«Вы должны увеличивать его, если текущая версия вашего класса не обратно совместима с его предыдущей версией:« Сначала вы должны изучить расширенную поддержку Serialization для управления версиями объектов, (a) чтобы убедиться, что класс действительно теперь несовместим с сериализацией, чего по спецификации довольно сложно достичь; (б) попробовать схему, такую ​​как пользовательские методы read / writeObject (), методы readResolve / writeReplace (), объявления serializableFields и т. д., чтобы убедиться, что поток остается совместимым. Изменение факта serialVersionUID- последнее средство, совет отчаяния.
Маркиз Лорн
4
Инкремент @EJP serialVersionUID появляется, когда первоначальный автор класса ввел явно. Я бы сказал, сгенерированный jvm серийный идентификатор должен быть в порядке. это лучший ответ, который я видел на сериализации.
сверхобмена
307

Я не могу упустить эту возможность, чтобы включить книгу Джоша Блоха « Эффективная Java» (2-е издание). Глава 11 является обязательным ресурсом по сериализации Java.

Согласно Джошу, автоматически генерируемый UID генерируется на основе имени класса, реализованных интерфейсов и всех открытых и защищенных членов. Изменение любого из них любым способом изменит serialVersionUID. Таким образом, вам не нужно связываться с ними, только если вы уверены, что не более одной версии класса никогда не будет сериализовано (либо между процессами, либо получено из хранилища позднее).

Если вы игнорируете их сейчас и обнаружите позже, что вам нужно каким-то образом изменить класс, но сохранить совместимость со старой версией класса, вы можете использовать инструментальный сериал JDK для генерации serialVersionUIDна старом классе и явно установить, что на новый класс. (В зависимости от ваших изменений вам может потребоваться также реализовать пользовательскую сериализацию, добавив writeObjectи readObjectметоды - см. SerializableJavadoc или вышеупомянутую главу 11.)

Скотт Бэйл
источник
33
Таким образом, можно было бы беспокоиться о SerializableVersionUID, если бы вы были обеспокоены совместимостью со старыми версиями класса?
Зигги
10
Да, в случае, если более новая версия изменит какой-либо открытый член на защищенный, SerializableVersionUID по умолчанию будет другим и вызовет InvalidClassExceptions.
Чандер Шивдасани
3
Имя класса, реализованные интерфейсы, все открытые и защищенные методы, ВСЕ переменные экземпляра.
Achow
31
Стоит отметить, что Джошуа Блох советует, что для каждого класса Serializable стоит указывать серийную версию uid. Цитата из главы 11. Независимо от того, какую сериализованную форму вы выбираете, объявляйте явный UID последовательной версии в каждом сериализуемом классе, который вы пишете. Это исключает UID серийной версии как потенциальный источник несовместимости (элемент 74). Существует также небольшое преимущество в производительности. Если серийный номер UID не указан, для его генерации во время выполнения требуются дорогостоящие вычисления.
Ашутош Джиндал
135

Вы можете указать Eclipse игнорировать эти предупреждения serialVersionUID:

Окно> Настройки> Java> Компилятор> Ошибки / Предупреждения> Потенциальные проблемы программирования

В случае, если вы не знали, есть много других предупреждений, которые вы можете включить в этом разделе (или даже сообщить о некоторых ошибках), многие из них очень полезны:

  • Потенциальные проблемы программирования: возможное случайное логическое назначение
  • Потенциальные проблемы программирования: доступ с нулевым указателем
  • Ненужный код: локальная переменная никогда не читается
  • Ненужный код: избыточная нулевая проверка
  • Ненужный код: Ненужный приведение или 'instanceof'

и многое другое.

Мэтт Б
источник
21
upvote, но только потому, что оригинальный постер ничего не сериализует. Если бы на плакате говорилось: «Я сериализирую эту штуку и ...», тогда вместо этого вы получите голос ниже: P
Джон Гарднер
3
Правда - я предполагал, что спрашивающий просто не хотел, чтобы его предупреждали
Мэтт Б
14
@Gardner -> согласился! Но спрашивающий также хочет знать, почему он не хочет, чтобы его предупреждали.
Зигги
8
Спрашивающий, очевидно, заботится о том, почему должен быть UID. Так что просто сказать ему, чтобы он проигнорировал это предупреждение, следует отвергнуть.
CinqS
111

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

Если вы хотите создать версию ваших данных, вы обычно начинаете с serialVersionUID0 и увеличиваете его при каждом структурном изменении вашего класса, которое изменяет сериализованные данные (добавляя или удаляя непереходные поля).

Встроенный механизм десериализации ( in.defaultReadObject()) откажется от десериализации старых версий данных. Но если вы хотите, вы можете определить свою собственную функцию readObject (), которая может считывать старые данные. Этот пользовательский код может затем проверить serialVersionUID, чтобы узнать, в какой версии находятся данные, и решить, как десериализовать их. Этот метод управления версиями полезен, если вы храните сериализованные данные, которые сохраняются в нескольких версиях вашего кода.

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

В этом случае вы не заинтересованы в поддержании обратной совместимости. Вы заинтересованы только в том, чтобы убедиться, что базы кода, которые общаются, действительно имеют одинаковые версии соответствующих классов. Чтобы упростить такую ​​проверку, вы должны поддерживать то же, serialVersionUIDчто и раньше, и не забывать обновлять ее при внесении изменений в ваши классы.

Если вы забудете обновить поле, вы можете получить две разные версии класса с разной структурой, но с одинаковой serialVersionUID. Если это произойдет, механизм по умолчанию ( in.defaultReadObject()) не обнаружит различий и попытается десериализовать несовместимые данные. Теперь вы можете получить загадочную ошибку во время выполнения или тихий сбой (пустые поля). Эти типы ошибок могут быть трудно найти.

Таким образом, чтобы помочь этому варианту использования, платформа Java предлагает вам выбор не устанавливать serialVersionUIDвручную. Вместо этого, хэш структуры класса будет сгенерирован во время компиляции и использован как идентификатор. Этот механизм гарантирует, что у вас никогда не будет разных структур классов с одинаковым идентификатором, и поэтому вы не получите этих трудно отслеживаемых ошибок сериализации во время выполнения, упомянутых выше.

Но есть и обратная сторона автоматически генерируемой стратегии id. А именно, что сгенерированные идентификаторы для одного и того же класса могут отличаться в разных компиляторах (как упомянуто Джоном Скитом выше). Поэтому, если вы передаете сериализованные данные между кодом, скомпилированным с разными компиляторами, рекомендуется в любом случае поддерживать идентификаторы вручную.

И если вы обратно совместимы с вашими данными, как в первом упомянутом случае использования, вы также, вероятно, захотите сохранить идентификатор самостоятельно. Это для того, чтобы получить читаемые идентификаторы и иметь больший контроль над тем, когда и как они меняются.

Александр Торстлинг
источник
4
Добавление или удаление непереходных полей не делает сериализацию несовместимой с классом. Таким образом, нет никаких причин «наталкиваться» на такие изменения.
Маркиз Лорн
4
@EJP: А? Добавление данных определенно меняет данные сериализации в моем мире.
Александр Торстлинг
3
@AlexanderTorstling Прочитайте, что я написал. Я не говорил, что это не «меняет данные сериализации». Я сказал, что это не делает сериализацию несовместимой с сериализацией. Это не одно и то же. Вам необходимо прочитать главу «Управление версиями» в Спецификации сериализации объектов.
маркиз Лорн
3
@EJP: я понимаю, что добавление непереходного поля не обязательно означает, что вы делаете сериализацию несовместимой с классом, но это структурное изменение, которое изменяет сериализованные данные, и вы обычно хотите поднять версию при этом, если только вы обращаться с обратной совместимостью, что я также объяснил позже в посте. Какова ваша точка зрения именно?
Александр Торстлинг
4
Моя точка зрения остается именно тем, что я сказал. Добавление или удаление непереходных полей не делает класс несовместимым с сериализацией. Поэтому вам не нужно каждый раз увеличивать serialVersionUID.
Маркиз Лорн
72

Что такое serialVersionUID и почему я должен его использовать?

SerialVersionUIDявляется уникальным идентификатором для каждого класса, JVMиспользует его для сравнения версий класса, обеспечивая, чтобы тот же класс, который использовался во время сериализации, загружался во время десериализации.

Указание одного дает больше контроля, хотя JVM генерирует его, если вы не укажете. Сгенерированное значение может отличаться для разных компиляторов. Кроме того, иногда вы просто хотите по какой-то причине запретить десериализацию старых сериализованных объектов [ backward incompatibility], и в этом случае вам просто нужно изменить serialVersionUID.

В Javadocs дляSerializable говорят :

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

Следовательно, вы должны объявить serialVersionUID, потому что это дает нам больше контроля .

Эта статья имеет несколько положительных моментов по теме.

Thalaivar
источник
3
@Vinothbabu, но serialVersionUID является статическим, поэтому статические переменные нельзя сериализовать. тогда почему jvm проверит версию, не зная, какая версия объекта десериализации
Kranthi Sama
3
Единственное, что не упомянуто в этом ответе, - это то, что вы можете вызвать непредвиденные последствия, слепо включив, serialVersionUIDне зная, почему. Комментарий Тома Андерсона к ответу MetroidFan2002 касается этого: «Я бы сказал, что если вы не используете сериализацию для постоянного хранения, вам следует использовать @SuppressWarnings вместо добавления значения. Это меньше загромождает класс и сохраняет способность Механизм serialVersionUID защитит вас от несовместимых изменений. "
Кирби
7
serialVersionUIDэто не а «уникальный идентификатор для каждого класса». Полное имя класса таково. Это индикатор версии .
Маркиз Лорн
58

В первоначальном вопросе спрашивалось «почему это важно» и «пример», где это Serial Version IDбыло бы полезно. Ну, я нашел один.

Скажем, вы создаете Carкласс, создаете его экземпляр и записываете его в поток объектов. Расплющенный объект автомобиля некоторое время находится в файловой системе. Между тем, если Carкласс изменяется, добавляя новое поле. Позже, когда вы попытаетесь прочитать (т.е. десериализовать) уплощенный Carобъект, вы получите java.io.InvalidClassException- потому что всем сериализуемым классам автоматически присваивается уникальный идентификатор. Это исключение выдается, когда идентификатор класса не равен идентификатору сплющенного объекта. Если вы действительно думаете об этом, исключение выдается из-за добавления нового поля. Вы можете избежать этого исключения, управляя версионированием самостоятельно, объявив явный serialVersionUID. Существует также небольшое преимущество в производительности в явном объявлении вашегоserialVersionUID(потому что не нужно рассчитывать). Поэтому рекомендуется добавлять свой собственный serialVersionUID в свои классы Serializable, как только вы создадите их, как показано ниже:

public class Car {
    static final long serialVersionUID = 1L; //assign a long value
}
Rupesh
источник
И каждому UID следует назначать случайное длинное число, а не 1L.
аббас
4
@abbas «Нужно» сделать это, почему? Пожалуйста, объясните, какая разница.
маркиз Лорн
Как следует из названия, оно представляет версию класса, которая использовалась для сериализации объекта. Если вы назначаете ему один и тот же номер каждый раз, вы не сможете найти подходящий класс для десериализации. Поэтому, когда вы вносите изменения в класс, лучше тоже изменить версию.
аббас
2
@abbas, это намерение не противоречит использованию увеличивающихся натуральных чисел из 1и так далее.
Вадим
2
@BillK, я думал, что проверка сериализации связана с парой classname и serialVersionUID. Так что разные схемы нумерации разных классов и библиотек никак не могут мешать. Или вы подразумевали библиотеки, генерирующие код?
Вадим
44

Сначала я должен объяснить, что такое сериализация.

Сериализация позволяет преобразовать объект в поток для отправки этого объекта по сети ИЛИ сохранить в файл ИЛИ сохранить в БД для использования в письме.

Есть несколько правил для сериализации .

  • Объект является сериализуемым, только если его класс или его суперкласс реализуют интерфейс Serializable.

  • Объект является сериализуемым (сам реализует интерфейс Serializable), даже если его суперкласс - нет. Однако первый суперкласс в иерархии сериализуемого класса, который не реализует интерфейс Serializable, ДОЛЖЕН иметь конструктор без аргументов. Если это нарушено, readObject () создаст исключение java.io.InvalidClassException во время выполнения.

  • Все примитивные типы являются сериализуемыми.

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

  • Статические поля (со статическим модификатором) не сериализуются.

Когда Objectсериализуется, Java Runtime связывает серийный номер версии, иначе serialVersionID.

Где нам нужен serialVersionID: во время десериализации, чтобы убедиться, что отправитель и получатель совместимы с сериализацией. Если получатель загрузил класс с другим,serialVersionIDто десериализация закончитсяInvalidClassCastException.
Сериализуемый класс можетserialVersionUIDявнообъявить свой собственный, объявив поле с именем,serialVersionUIDкоторое должно быть статическим, конечным и типа long.

Давайте попробуем это на примере.

import java.io.Serializable;    
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String empname;
private byte empage;

public String getEmpName() {
    return name;
}
public void setEmpName(String empname) {
    this.empname = empname;
}
public byte getEmpAge() {
    return empage;
}
public void setEmpAge(byte empage) {
    this.empage = empage;
}

public String whoIsThis() {
    StringBuffer employee = new StringBuffer();
    employee.append(getEmpName()).append(" is ).append(getEmpAge()).append("
years old  "));
    return employee.toString();
}
}

Создать объект сериализации

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Writer {
public static void main(String[] args) throws IOException {
    Employee employee = new Employee();
    employee.setEmpName("Jagdish");
    employee.setEmpAge((byte) 30);

    FileOutputStream fout = new 
FileOutputStream("/users/Jagdish.vala/employee.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fout);
    oos.writeObject(employee);
    oos.close();
    System.out.println("Process complete");
}
}

Десериализовать объект

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Reader {
public static void main(String[] args) throws ClassNotFoundException, 
IOException {
    Employee employee = new Employee();
    FileInputStream fin = new 
    FileInputStream("/users/Jagdish.vala/employee.obj");
    ObjectInputStream ois = new ObjectInputStream(fin);
    employee = (Employee) ois.readObject();
    ois.close();
    System.out.println(employee.whoIsThis());
 }
}    

ПРИМЕЧАНИЕ. Теперь измените serialVersionUID класса Employee и сохраните:

private static final long serialVersionUID = 4L;

И выполнить класс Reader. Не выполнять класс Writer, и вы получите исключение.

Exception in thread "main" java.io.InvalidClassException: 
com.jagdish.vala.java.serialVersion.Employee; local class incompatible: 
stream classdesc serialVersionUID = 1, local class serialVersionUID = 4
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.krishantha.sample.java.serialVersion.Reader.main(Reader.java:14)
JegsVala
источник
Поправьте меня, если я не прав - локальный класс - это тот, который вы в настоящее время используете / используете в пути к классам, а поток - это тот, который используется другой стороной (например, сервер, который возвращает вам ответ и сериализовал ответ). Вы можете столкнуться с такой ситуацией, когда вы общаетесь с сервером, который обновил свои сторонние библиотеки, но вы (клиент) этого не сделали.
Виктор
37

Если вам никогда не понадобится сериализовать ваши объекты в байтовый массив и отправить / сохранить их, вам не нужно об этом беспокоиться. Если вы это сделаете, то вы должны рассмотреть ваш serialVersionUID, поскольку десериализатор объекта будет сопоставлять его с версией объекта, которую имеет его загрузчик классов. Узнайте больше об этом в Спецификации языка Java.

eishay
источник
10
Если вы не собираетесь сериализовать объекты, почему они сериализуются?
Эриксон
7
@erickson - родительский класс может быть сериализуемым, скажем, ArrayList, но вы хотите, чтобы ваш собственный объект (скажем, список измененных массивов) использовал его в качестве базы, но никогда не будет сериализовать созданную вами коллекцию.
MetroidFan2002
5
Это не упоминается нигде в спецификации языка Java. Это упомянуто в спецификации управления версиями объекта.
Маркиз Лорн
4
Вот ссылка на Спецификацию управления версиями объекта Java 8 .
Базилик Бурк
34

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

Итак, вместо

public class MyExample extends ArrayList<String> {

    public MyExample() {
        super();
    }
    ...
}

делать

public class MyExample {
    private List<String> myList;

    public MyExample() {
         this.myList = new ArrayList<String>();
    }
    ...
}

и в соответствующих вызовах методов myList.foo() вместо this.foo()(или super.foo()). (Это подходит не во всех случаях, но все же довольно часто.)

Я часто вижу, как люди расширяют JFrame или что-то подобное, когда им действительно нужно делегировать это. (Это также помогает для автозаполнения в IDE, поскольку в JFrame есть сотни методов, которые вам не нужны, когда вы хотите вызывать свои собственные в своем классе.)

Один случай, когда предупреждение (или serialVersionUID) является неизбежным, - это когда вы расширяетесь из AbstractAction, обычно в анонимном классе, только добавляя actionPerformed-метод. Я думаю, что в этом случае не должно быть предупреждения (поскольку в любом случае вы не можете надежно сериализовать и десериализовать такие анонимные классы в любом случае в разных версиях вашего класса), но я не уверен, как компилятор может это распознать.

Пауло Эберманн
источник
4
Я думаю, что вы правы, что состав по сравнению с наследованием имеет больше смысла, особенно когда вы обсуждаете такие классы, как ArrayList. Тем не менее, многие фреймворки требуют, чтобы люди выходили из абстрактных суперклассов, которые можно сериализовать (например, класс ActionForm в Struts 1.2 или Saxon ExtensionFunctionDefinition), и в этом случае это решение невозможно. Я думаю, что вы правы, было бы неплохо, если бы предупреждение было проигнорировано в некоторых случаях (например, если вы выходили из абстрактного сериализуемого класса)
piepera
2
Конечно, если вы добавляете класс в качестве члена, а не наследуете его, вам придется написать метод-обертку для КАЖДОГО метода класса-члена, который вы хотите использовать, что сделает его невозможным в большом количестве ситуаций. если у java нет функций, похожих на perl __AUTOLOAD, о которых я не знаю.
M_M
4
@M_M: Когда вы делегируете множество методов для вашего обернутого объекта, конечно, было бы нецелесообразно использовать делегирование. Но я предполагаю, что этот случай является признаком ошибки проектирования - пользователям вашего класса (например, «MainGui») не нужно вызывать множество методов обернутого объекта (например, JFrame).
Пауло Эберманн
1
Что мне не нравится в делегировании, так это необходимость держать ссылку на делегата. И каждая ссылка означает больше памяти. Поправьте меня если я ошибаюсь. Если мне нужен CustomizedArrayList из 100 объектов, это не будет иметь большого значения, но если мне понадобятся сотни CustomizdeArrayList из нескольких объектов, использование памяти значительно возрастет.
WVrock
2
Throwable является сериализуемым, и только Throwable является бросаемым, поэтому невозможно определить исключение, которое не сериализуемо. Делегирование невозможно.
Фил
22

Чтобы понять значение поля serialVersionUID, необходимо понять, как работает сериализация / десериализация.

Когда сериализуемый объект класса сериализуется, Java Runtime связывает серийный номер версии (называемый serialVersionUID) с этим сериализованным объектом. Во время десериализации этого сериализованного объекта среда выполнения Java сопоставляет serialVersionUID сериализованного объекта с serialVersionUID класса. Если оба равны, то только он продолжается с дальнейшим процессом десериализации, иначе выдает InvalidClassException.

Таким образом, мы заключаем, что для успешного выполнения процесса сериализации / десериализации serialVersionUID сериализованного объекта должен быть эквивалентен serialVersionUID класса. В случае, если программист явно указывает значение serialVersionUID в программе, то это же значение будет связано с сериализованным объектом и классом, независимо от платформы сериализации и десериализации (например, сериализация может быть выполнена на платформе, такой как windows, с использованием sun или MS JVM и десериализация могут быть на разных платформах Linux с использованием Zing JVM).

Но в случае, если serialVersionUID не указан программистом, тогда при выполнении Serialization \ DeSerialization какого-либо объекта среда выполнения Java использует свой собственный алгоритм для его вычисления. Этот алгоритм вычисления serialVersionUID варьируется от одного JRE к другому. Также возможно, что среда, в которой объект сериализуется, использует одну JRE (например, SUN JVM), а среда, в которой происходит десериализация, использует Linux Jvm (zing). В таких случаях serialVersionUID, связанный с сериализованным объектом, будет отличаться от serialVersionUID класса, рассчитанного в среде десериализации. В свою очередь десериализация не будет успешной. Поэтому, чтобы избежать таких ситуаций / проблем, программист должен всегда указывать serialVersionUID класса Serializable.

Нитеш Сони
источник
2
Алгоритм не меняется, но он немного недооценен.
маркиз Лорн
... алгоритм не меняется, но он немного недооценен ... то есть любой jvm может отличаться ..... @ user207421
AnthonyJClink
18

Не беспокойтесь, расчет по умолчанию действительно хорош и достаточен для 99,9999% случаев. И если вы столкнетесь с проблемами, вы можете - как уже говорилось - вводить UID как необходимость (что крайне маловероятно)

Grand Johnson
источник
2
Мусор. Это достаточно для случая, когда класс не изменился. У вас нет доказательств в поддержку «99,9999%».
маркиз Лорн
18

Что касается примера, где отсутствующий serialVersionUID может вызвать проблему:

Я работаю над этим приложением Java EE, которое состоит из веб-модуля, который использует EJBмодуль. Веб-модуль вызывает EJBмодуль удаленно и передает ему, POJOкоторый реализуетSerializable в качестве аргумента.

Эта POJO's класс был упакован внутри EJB-файла и внутри его собственного файла в WEB-INF / lib веб-модуля. На самом деле это один и тот же класс, но когда я упаковываю модуль EJB, я распаковываю этот jar-файл POJO, чтобы упаковать его вместе с модулем EJB.

Вызов к EJBсбою с Исключением ниже, потому что я не объявил его serialVersionUID:

Caused by: java.io.IOException: Mismatched serialization UIDs : Source
 (Rep.
 IDRMI:com.hordine.pedra.softbudget.domain.Budget:5CF7CE11E6810A36:04A3FEBED5DA4588)
 = 04A3FEBED5DA4588 whereas Target (Rep. ID RMI:com.hordine.pedra.softbudget.domain.Budget:7AF5ED7A7CFDFF31:6227F23FA74A9A52)
 = 6227F23FA74A9A52
Энрике Ордин
источник
16

Я обычно использую serialVersionUIDв одном контексте: когда я знаю, это будет выходить из контекста Java VM.

Я знал бы это, когда я буду использовать ObjectInputStreamи ObjectOutputStreamдля моего приложения, или если я знаю, что библиотека / среда, которую я использую, будет использовать это. SerialVersionID гарантирует, что разные виртуальные машины Java разных версий или поставщиков будут правильно взаимодействовать или, например, если они хранятся и извлекаются вне виртуальной машиныHttpSession данные сеанса могут оставаться даже во время перезапуска и обновления сервера приложений.

Для всех остальных случаев я использую

@SuppressWarnings("serial")

так как большую часть времени по умолчанию serialVersionUIDдостаточно. Это включает в себя Exception, HttpServlet.

Архимед Траяно
источник
Он не включает HttpServlet в контейнеры, где их можно заменить, или исключение в RMI, например.
маркиз Лорн
14

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

Если вы не хотите, чтобы это предупреждение появилось, используйте это:

@SuppressWarnings("serial")
Mukti
источник
11

Было бы неплохо, если бы CheckStyle мог проверить, что serialVersionUID в классе, который реализует Serializable, имеет хорошее значение, то есть соответствует тому, что будет генерировать генератор идентификатора последовательной версии. Если у вас есть проект с большим количеством сериализуемых DTO, например, не забудьте удалить существующий serialVersionUID и восстановить его, это боль, и в настоящее время единственный (который я знаю) способ проверить это - восстановить для каждого класса и сравнить с Старый. Это очень очень больно.


источник
8
Если вы устанавливаете serialVersionUID всегда на одно и то же значение, которое генерирует генератор, вам это вообще не нужно. В конце концов, его смысл - оставаться неизменным после изменений, когда класс все еще совместим.
Paŭlo Ebermann
4
Причина в том, что разные компиляторы имеют одинаковое значение для одного и того же класса. Как объяснено в javadocs (также ответил выше), сгенерированная версия является хрупкой и может варьироваться, даже если класс должным образом десериализуем. Пока вы каждый раз запускаете этот тест на одном и том же компиляторе, он должен быть безопасным. Бог поможет вам, если вы обновите JDK и появится новое правило, даже если ваш код не изменился.
Эндрю Бакер
2
Это не обязательно соответствует тому, что serialverбудет производить. -1
Маркиз Лорн
В общем, это совсем не обязательно. В случае @ AndrewBacker потребовалось бы два разных компилятора в одном и том же файле .java, при этом обе версии файлов .class взаимодействовали друг с другом - в большинстве случаев вы просто создаете класс и распространяете его. Если это так, то отсутствие SUID будет работать нормально.
Билл К
1
Люди, которые действительно используют сериализацию для целей хранения / извлечения, обычно устанавливают на serialVersionUID1. Если более новая версия класса несовместима, но все еще требует, чтобы иметь возможность обрабатывать старые данные, вы увеличиваете номер версии и добавляете специальный код в иметь дело со старыми форматами. Я плачу каждый раз, когда вижу serialVersionUIDбольше 1 цифры, либо потому, что это было случайное число (бесполезное), либо потому, что класс, очевидно, должен иметь дело с более чем 10 различными версиями.
john16384
11

SerialVersionUID используется для контроля версий объекта. вы также можете указать serialVersionUID в вашем файле класса. Следствием отсутствия указания serialVersionUID является то, что при добавлении или изменении какого-либо поля в классе уже сериализованный класс не сможет восстановиться, поскольку serialVersionUID, сгенерированный для нового класса и для старого сериализованного объекта, будет отличаться. Процесс сериализации Java использует правильный serialVersionUID для восстановления состояния сериализованного объекта и создает исключение java.io.InvalidClassException в случае несовпадения serialVersionUID

Читать подробнее: http://javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZ

Ниту
источник
9

Зачем использовать SerialVersionUIDвнутри Serializableкласса в Java?

Во serializationвремя выполнения Java создает номер версии для класса, чтобы он мог десериализовать его позже. Этот номер версии известен как SerialVersionUIDв Java.

SerialVersionUIDиспользуется для версии сериализованных данных. Вы можете десериализовать класс только в том случае, если он SerialVersionUIDсовпадает с сериализованным экземпляром. Когда мы не объявляем SerialVersionUIDв нашем классе, среда выполнения Java генерирует его для нас, но это не рекомендуется. Рекомендуется объявлять SerialVersionUIDкак private static final longпеременную, чтобы избежать механизма по умолчанию.

Когда вы объявляете класс как Serializableреализующий интерфейс маркера java.io.Serializable, среда выполнения Java сохраняет экземпляр этого класса на диске, используя механизм сериализации по умолчанию, при условии, что вы не настроили процесс с использованием Externalizableинтерфейса.

см. также Зачем использовать SerialVersionUID внутри класса Serializable в Java

roottraveller
источник
8

Если вы хотите изменить огромное количество классов, для которых не был установлен serialVersionUID, в первую очередь, при сохранении совместимости со старыми классами, такие инструменты, как IntelliJ Idea, Eclipse не дотягивают, так как они генерируют случайные числа и не работают с кучей файлов. на одном дыхании. Я подошел к следующему скрипту bash (извините за пользователей Windows, подумайте о покупке Mac или о переходе на Linux), чтобы легко исправить проблему serialVersionUID:

base_dir=$(pwd)                                                                  
src_dir=$base_dir/src/main/java                                                  
ic_api_cp=$base_dir/target/classes                                               

while read f                                                                     
do                                                                               
    clazz=${f//\//.}                                                             
    clazz=${clazz/%.java/}                                                       
    seruidstr=$(serialver -classpath $ic_api_cp $clazz | cut -d ':' -f 2 | sed -e 's/^\s\+//')
    perl -ni.bak -e "print $_; printf qq{%s\n}, q{    private $seruidstr} if /public class/" $src_dir/$f
done

Вы сохраняете этот скрипт, скажем add_serialVersionUID.sh вам ~ / bin. Затем вы запускаете его в корневом каталоге вашего проекта Maven или Gradle, например:

add_serialVersionUID.sh < myJavaToAmend.lst

Это .lst включает в себя список Java-файлов для добавления serialVersionUID в следующем формате:

com/abc/ic/api/model/domain/item/BizOrderTransDO.java
com/abc/ic/api/model/domain/item/CardPassFeature.java
com/abc/ic/api/model/domain/item/CategoryFeature.java
com/abc/ic/api/model/domain/item/GoodsFeature.java
com/abc/ic/api/model/domain/item/ItemFeature.java
com/abc/ic/api/model/domain/item/ItemPicUrls.java
com/abc/ic/api/model/domain/item/ItemSkuDO.java
com/abc/ic/api/model/domain/serve/ServeCategoryFeature.java
com/abc/ic/api/model/domain/serve/ServeFeature.java
com/abc/ic/api/model/param/depot/DepotItemDTO.java
com/abc/ic/api/model/param/depot/DepotItemQueryDTO.java
com/abc/ic/api/model/param/depot/InDepotDTO.java
com/abc/ic/api/model/param/depot/OutDepotDTO.java

Этот скрипт использует инструмент JDK serialVer под капотом. Поэтому убедитесь, что ваш $ JAVA_HOME / bin находится в PATH.

schnell18
источник
2
Дает мне идею: всегда регенерируйте uid серийной версии с помощью инструмента, подобного этому, никогда не делайте вручную, прежде чем делать релиз - таким образом, вы не забудете внести изменения в класс, чей uid серийной версии должен был измениться из-за фактической несовместимое изменение. Отслеживать это вручную очень сложно.
Игнацио
6

Этот вопрос очень хорошо задокументирован в книге «Эффективная Java» Джошуа Блоха. Очень хорошая книга, которую нужно прочитать. Я изложу некоторые из причин ниже:

Среда выполнения сериализации предлагает номер, называемый последовательной версией, для каждого сериализуемого класса. Этот номер называется serialVersionUID. Теперь за этим числом стоит некоторая математика, и она основана на полях / методах, определенных в классе. Для одного и того же класса каждый раз генерируется одна и та же версия. Этот номер используется во время десериализации для проверки того, что отправитель и получатель сериализованного объекта загрузили классы для этого объекта, которые совместимы в отношении сериализации. Если получатель загрузил класс для объекта, который имеет serialVersionUID, отличный от класса соответствующего отправителя, то десериализация приведет к исключению InvalidClassException.

Если класс является сериализуемым, вы также можете явно объявить свой собственный serialVersionUID, объявив поле с именем «serialVersionUID», которое должно быть статическим, конечным и типа long. Большинство IDE, таких как Eclipse, помогают вам генерировать эту длинную строку.

Фанат
источник
6

Каждый раз, когда объект сериализуется, объект помечается идентификатором версии для класса объекта. Этот идентификатор называется serialVersionUID и вычисляется на основе информации о структуре класса. Предположим, вы создали класс Employee и у него есть идентификатор версии # 333 (назначенный JVM). Теперь, когда вы будете сериализовать объект этого класса (предположим, объект Employee), JVM назначит ему UID как # 333.

Рассмотрим ситуацию - в будущем вам необходимо отредактировать или изменить свой класс, и в этом случае, когда вы его измените, JVM назначит ему новый UID (предположим, # 444). Теперь, когда вы попытаетесь десериализовать объект employee, JVM сравнит идентификатор версии сериализованного объекта (объекта Employee) (# 333) с идентификатором класса, т. Е. # 444 (поскольку он был изменен). При сравнении JVM обнаружит, что обе версии UID различаются, и, следовательно, десериализация не удастся. Следовательно, если serialVersionID для каждого класса определяется самим программистом. Это будет то же самое, даже если класс будет развиваться в будущем, и, следовательно, JVM всегда обнаружит, что класс совместим с сериализованным объектом, даже если этот класс изменен. Для получения дополнительной информации вы можете обратиться к главе 14 HEAD FIRST JAVA.

Навед Али
источник
1
Каждый раз, когда класс объекта сериализуется, serialVersionUIDпередается. Это не отправлено с каждым объектом.
маркиз Лорн
3

Простое объяснение:

  1. Вы сериализуете данные?

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

  2. Собираетесь ли вы пойти в производство?

    Если вы просто тестируете что-то с неважными / поддельными данными, не беспокойтесь об этом (если только вы не тестируете сериализацию напрямую).

  3. Это первая версия?

    Если так, установите serialVersionUID=1L.

  4. Это вторая, третья и т. Д. Версия?

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

По сути, если вы не обновите версию правильно, когда вы обновляете класс, который вам нужен для записи / чтения, вы получите ошибку при попытке прочитать старые данные.

gagarwa
источник
2

'serialVersionUID' - это 64-разрядное число, используемое для уникальной идентификации класса в процессе десериализации. Когда вы сериализуете объект, serialVersionUID класса также записывается в файл. Всякий раз, когда вы десериализуете этот объект, среда выполнения Java извлекает это значение serialVersionUID из сериализованных данных и сравнивает то же значение, связанное с классом. Если оба не совпадают, то будет выброшено исключение java.io.InvalidClassException.

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

Хари Кришна
источник
1

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

  • у вас есть много разных копий вашей программы в разных местах (например, 1 сервер и 100 клиентов). Если вы измените свой объект, измените свой номер версии и забудете обновить один из этих клиентов, он будет знать, что он не способен к десериализации

  • вы сохранили свои данные в каком-то файле, и позже вы попытаетесь открыть их с обновленной версией вашей программы с измененным объектом - вы будете знать, что этот файл несовместим, если вы сохраните правильную версию

Когда это важно?

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

Менее очевидно - при десериализации объекта поля, которые не представлены в строке, будут сохранены как NULL. Если вы удалили поле из вашего объекта, более старые версии будут сохранять это поле как всегда-NULL, что может привести к неправильному поведению, если более старые версии полагаются на данные в этом поле (в любом случае вы создали его для чего-то, а не просто для удовольствия :-))

Наименее очевидный - иногда вы меняете идею, которую вы вкладываете в значение некоторой области. Например, когда вам 12 лет, вы подразумеваете «велосипед» под словом «велосипед», но когда вам 18 лет, вы подразумеваете «мотоцикл» - если ваши друзья пригласят вас на «поездку на велосипеде по городу», и вы будете единственным, кто Приехал на велосипеде, вы поймете, как важно сохранять одинаковое значение в разных областях :-)

Станислав Орлов
источник
1

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

Сериализация: мы часто работаем с важными объектами, состояние которых (данные в переменных объекта) настолько важно, что мы не можем рискнуть потерять их из-за сбоев питания / системы (или) сбоев сети в случае отправки состояния объекта другому машина. Решение этой проблемы называется «Постоянство», что просто означает сохранение (хранение / сохранение) данных. Сериализация является одним из многих других способов достижения постоянства (путем сохранения данных на диск / в память). При сохранении состояния объекта важно создать идентичность для объекта, чтобы иметь возможность правильно прочитать его обратно (десериализация). Этот уникальный идентификатор ID является SerialVersionUID.

Шенкс Д Шива
источник
0

Что такое SerialVersionUID? Ответ: - Допустим, есть два человека, один из штаб-квартиры, а другой из ODC, оба будут выполнять сериализацию и десериализацию соответственно. В этом случае для проверки подлинности того, что получатель, находящийся в ODC, является проверенным лицом, JVM создает уникальный идентификатор, известный как SerialVersionUID.

Вот хорошее объяснение, основанное на сценарии,

Почему SerialVersionUID?

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

Десериализация : во время десериализации JVM на стороне получателя будет сравнивать уникальный идентификатор, связанный с объектом, с уникальным идентификатором локального класса, т. Е. JVM будет также создавать уникальный идентификатор на основе соответствующего файла .class, который присутствует в системе получателя. Если оба уникальных идентификатора совпадают, то будет выполняться только десериализация. В противном случае мы получим исключение времени выполнения, говорящее InvalidClassException. Этот уникальный идентификатор - не что иное, как SerialVersionUID

forkdbloke
источник