Що таке serialVersionUID і чому я повинен його використовувати?


2998

Eclipse видає попередження, коли serialVersionUIDвідсутня а.

Клас Foo, що серіалізується, не оголошує статичне кінцеве поле serialVersionUID типу long

Що таке serialVersionUIDі чому це важливо? Покажіть приклад, коли відсутність serialVersionUIDпризведе до проблеми.


1
Знайдіть хорошу практику щодо serialversionUID; dzone.com/articles/what-is-serialversionuid
ceyun

Відповіді:


2289

Документи для 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Декларації використовують приватний модифікатор, де це можливо, оскільки такі декларації застосовуються лише до безпосередньо оголошених serialVersionUIDполів класу , не корисні як успадковані члени.


325
Отже, те, про що ви говорите по суті, це те, що якщо користувач не зрозумів усіх вищезазначених матеріалів, він не міг би турбуватися про серіалізацію? Я вважаю, ти відповів "як?" а не пояснювати "чому?". Я, наприклад, не розумію, чому я мав би турбуватися з SerializableVersionUID.
Ziggy

365
Причина полягає у другому абзаці: якщо ви чітко не вказуєте serialVersionUID, значення генерується автоматично - але це крихко, оскільки це залежить від реалізації компілятора.
Джон Скіт

14
І чому Eclipse каже, що мені потрібен "приватний статичний остаточний довгий serialVersionUID = 1L;" коли я продовжую клас винятку?
ДжонМерліно

21
@JohnMerlino: Ну, я б не очікував, що він скаже, що вам потрібен, але він може запропонувати той, щоб допомогти вам правильно серіалізувати винятки. Якщо ви не збираєтесь їх серіалізувати, вам дійсно не потрібна константа.
Джон Скіт

16
@JohnMerlino, щоб відповісти на те, чому частина вас викликає запитання: Exception реалізує Serializable і eclipse попереджає, що ви не встановили serialVersionUID, що було б хорошою ідеєю (якщо ви не хочете серіалізувати клас), щоб уникнути проблем, які постає в JonSkeet контури.
zpon

475

Якщо ви серіалізуєте лише тому, що вам доведеться серіалізувати заради реалізації (кому все одно, якщо ви серіалізуєте HTTPSession, наприклад ... якщо він зберігається чи ні, ви, мабуть, не переймаєтесь de-serializingоб'єктом форми), тоді ви можете ігноруйте це.

Якщо ви фактично використовуєте серіалізацію, це має значення лише в тому випадку, якщо ви плануєте зберігати та отримувати об'єкти безпосередньо за допомогою серіалізації. serialVersionUIDЯвляє ваш клас версії, і ви повинні збільшувати його , якщо поточна версія вашого класу не має зворотної сумісності з попередньою версією.

Більшу частину часу ви, ймовірно, не будете використовувати серіалізацію безпосередньо. У такому випадку створіть за замовчуванням SerialVersionUID, натиснувши на опцію швидкого виправлення, і не хвилюйтеся з цього приводу.


78
Я б сказав, що якщо ви не використовуєте серіалізацію для постійного зберігання, вам слід використовувати @SuppressWarnings, а не додавати значення. Він менше захаращує клас і зберігає прихильність механізму serialVersionUID, щоб захистити вас від несумісних змін.
Том Андерсон

25
Я не бачу, як додавання одного рядка (анотація @SuppressWarnings) на відміну від іншого рядка (ідентифікуючого ідентифікатора) "менше захаращує клас". І якщо ви не використовуєте серіалізацію для постійного зберігання, чому б ви просто не використали "1"? Ви б не піклувалися про автогенерований ідентифікатор у цьому випадку.
MetroidFan2002

71
@ MetroidFan2002: Я думаю, що пункт @ TomAnderson serialVersionUIDзахищає вас від несумісних змін є дійсним. Використання @SuppressWarningsдокументів краще, якщо ви не хочете використовувати клас для постійного зберігання.
AbdullahC

11
"Ви повинні збільшувати його, якщо поточна версія вашого класу не сумісна з попередньою версією:" Спершу слід вивчити широку підтримку серіалізації для версій об'єктів, (a) щоб переконатися, що клас справді тепер не сумісний із серіалізацією, чого за специфікацією досить складно досягти; (b) спробувати таку схему, як користувацькі методи read / writeObject (), readResolve / writeReplace (), декларації serializableFields тощо, щоб переконатися, що потік залишається сумісним. Зміна фактичного serialVersionUID- це крайній захід, порада відчаю.
user207421

4
Приріст @EJP serialVersionUID з'являється в картині, коли початковий автор класу явно ввів. Я б сказав, що jvm згенерований серійний ідентифікатор, має бути добре. це найкраща відповідь, яку я бачив при серіалізації.
переобмін

307

Я не можу пропустити цю можливість підключити книгу Джоша Блоха « Ефективна Java» (2-е видання). Розділ 11 - неодмінний ресурс серіалізації Java.

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

Якщо ви ігноруєте їх зараз, і знайти пізніше , що вам потрібно змінити клас в деякому роді , але зберегти сумісність з / стару версію класу, ви можете використовувати JDK інструмент serialver для генерації serialVersionUIDна старому класі, і явно вказати , що на новий клас. (Залежно від змін, можливо, вам також знадобиться здійснити спеціальну серіалізацію шляхом додавання writeObjectта readObjectметодів - див. SerializableJavadoc або вищезгаданий розділ 11.)


33
Тож можна потурбуватися про SerializableVersionUID, якщо хтось турбується про сумісність із старими версіями класу?
Ziggy

10
Так, якщо новіша версія змінить будь-який публічний член на захищений, SerializableVersionUID за замовчуванням буде іншим і підніме InvalidClassExceptions.
Чандер Шивдасані

3
Ім'я класу, реалізовані інтерфейси, усі відкриті та захищені методи, ВСІ змінні екземпляра.
Подання

31
Варто відзначити, що Джошуа Блох радить, що для кожного класу Serializable варто вказати uid серійної версії. Цитата з глави 11: Незалежно від того, яку серіалізовану форму ви обрали, оголосіть явний послідовний варіант UID у кожному серіалізаційному класі, який ви пишете. Це виключає UID серійної версії як потенційне джерело несумісності (Пункт 74). Також є невелика користь від продуктивності. Якщо UID серійної версії не передбачено, для створення цього під час виконання потрібні дорогі обчислення.
Ашутош Джиндал

136

Ви можете сказати Eclipse ігнорувати ці попередження serialVersionUID:

Вікно> Налаштування> Java> Компілятор> Помилки / попередження> Проблеми з потенційним програмуванням

Якщо ви цього не знаєте, у цьому розділі можна ввімкнути багато інших попереджень (або навіть повідомити про помилки), багато з яких дуже корисні:

  • Потенційні проблеми програмування: Можливе випадкове булеве призначення
  • Потенційні проблеми програмування: Нульовий доступ до вказівника
  • Непотрібний код: Локальна змінна ніколи не читається
  • Непотрібний код: Надлишкові нульові перевірки
  • Непотрібний код: Непотрібний формат чи "instanceof"

та багато іншого.


21
оновлення, але тільки тому, що, здається, оригінальний плакат нічого не серіалізує. Якщо б плакат сказав: "Я серіалізую цю річ і ...", тоді ви отримаєте голосування замість цього: P
Джон Гарднер

3
Правда - я припускав, що запитувача просто не хотіли, щоб його попередили
matt b

14
@Gardner -> домовились! Але запитуючий також хоче знати, чому він може не хотіти, щоб його попередили.
Ziggy

8
Опитувача, очевидно, хвилює питання, чому повинен бути UID. Тому просто говорити йому, що він ігнорує попередження, слід заборонити.
cinqS

111

serialVersionUIDполегшує версію серіалізованих даних. Його значення зберігається з даними при серіалізації. Під час десеріалізації та сама версія перевіряється, як серіалізовані дані відповідають поточному коду.

Якщо ви хочете оновлювати свої дані, зазвичай ви починаєте з serialVersionUID0 і додаєте їх до кожного структурного зміни до свого класу, який змінює серіалізовані дані (додавання або видалення непрохідних полів).

Вбудований механізм десеріалізації ( in.defaultReadObject()) відмовиться від десерталізації зі старих версій даних. Але якщо ви хочете, ви можете визначити власну функцію readObject (), яка може читати старі дані. Цей спеціальний код може потім перевірити serialVersionUIDвхід, щоб дізнатись, у якій версії є дані, і вирішити, як де-серіалізувати їх. Цей прийом версій корисний, якщо ви зберігаєте серіалізовані дані, які переживають кілька версій вашого коду.

Але зберігання серіалізованих даних протягом такого тривалого періоду часу не дуже поширене. Набагато частіше використовувати механізм серіалізації для тимчасового запису даних, наприклад, в кеш, або надсилання їх по мережі в іншу програму з тією ж версією відповідних частин кодової бази.

У цьому випадку ви не зацікавлені у підтримці зворотної сумісності. Ви стурбовані лише тим, щоб переконатися, що кодові бази, які передаються, дійсно мають однакові версії відповідних класів. Щоб полегшити таку перевірку, ви повинні підтримувати так serialVersionUIDсамо, як раніше, і не забувати оновлювати її, вносячи зміни до своїх класів.

Якщо ви забудете оновити поле, можливо, ви отримаєте дві різні версії класу з різною структурою, але з однаковою serialVersionUID. Якщо це трапиться, механізм за замовчуванням ( in.defaultReadObject()) не виявить різниці і спробує десертизувати несумісні дані. Тепер у вас може виникнути криптична помилка виконання або безшумний збій (нульові поля). Ці типи помилок можуть бути важко знайти.

Отож, щоб допомогти цьому випадку використання, платформа Java пропонує вам вибір не встановлювати serialVersionUIDвручну. Натомість хеш структури класу буде генеруватися під час компіляції та використовуватись як id. Цей механізм гарантує, що ви ніколи не маєте різних структур класу з однаковим ідентифікатором, і тому ви не отримаєте цих важко відстежуваних невдач серіалізації виконання, згаданих вище.

Але є зворотна сторона стратегії автоматичного створення ідентифікатора. А саме, що згенеровані ідентифікатори для одного класу можуть відрізнятися між компіляторами (як згадував Джон Скіт вище). Отже, якщо ви передаєте серіалізовані дані між кодом, зібраним з різними компіляторами, рекомендується підтримувати ідентифікатори вручну в будь-якому випадку.

І якщо ви назад сумісні з вашими даними, як, наприклад, у першому згаданому випадку використання, ви, ймовірно, хочете зберегти ідентифікатор самостійно. Це для того, щоб отримати читабельні ідентифікатори та мати більший контроль над тим, коли та як вони змінюються.


4
Додавання або видалення непрохідних полів не робить серіалізацію класів несумісною. Тому немає жодних причин «наштовхуватися на такі зміни».
користувач207421

4
@EJP: А? Додавання даних безумовно змінює дані серіалізації в моєму світі.
Олександр Торстлінг

3
@AlexanderTorstling Прочитайте, що я написав. Я не сказав, що це не «змінює дані про серіалізацію». Я сказав, що це "не робить серіалізацію класів несумісною". Це не те саме. Вам потрібно прочитати розділ Версія специфікації специфікації об'єктів.
користувач207421

3
@EJP: Я розумію, що додавання неперехідного поля не обов'язково означає, що ви робите серіалізацію класів несумісними, але це структурна зміна, яка змінює серіалізовані дані, і ви, як правило, хочете обробляти версію, виконуючи це, якщо ви не обробляти сумісність назад, що я також пояснив пізніше в дописі. Який саме ваш погляд?
Олександр Торстлінг

4
Моя суть залишається саме тим, що я сказав. Додавання або видалення непрохідних полів не робить клас серіалізації несумісним. Тому вам не потрібно нарізати serialVersionUID кожного разу, коли ви це робите.
користувач207421

72

Що таке serialVersionUID і чому я повинен його використовувати?

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

Визначення одного дає більше контролю, хоча JVM генерує його, якщо ви не вказуєте. Створене значення може відрізнятися між різними компіляторами. Крім того, іноді просто потрібно з якоїсь причини заборонити десеріалізацію старих серіалізованих об'єктів [ backward incompatibility], і в цьому випадку вам просто потрібно змінити serialVersionUID.

У Javadocs дляSerializable кажуть :

обчислення serialVersionUID за замовчуванням дуже чутливі до деталей класу, які можуть змінюватися залежно від реалізації компілятора, і, таким чином, можуть призвести до несподіваних InvalidClassExceptions під час десеріалізації.

Тому ви повинні оголосити serialVersionUID, оскільки це дає нам більше контролю .

У цій статті є кілька хороших моментів по темі.


3
@Vinothbabu, але serialVersionUID є статичним, тому статичні змінні неможливо серіалізувати. Тоді як jvm перевірить версію, не знаючи, яка версія об’єкта десеріалізації
Kranthi Sama

3
Єдина річ, що не згадується у цій відповіді, - це те, що ви можете спричинити ненавмисні наслідки сліпим включенням, serialVersionUIDне знаючи чому. Коментар Тома Андерсона щодо відповіді MetroidFan2002 стосується цього: "Я б сказав, що якщо ви не використовуєте серіалізацію для постійного зберігання, вам слід використовувати @SuppressWarnings, а не додавати значення. Це захаращує клас менше, і це зберігає здатність механізм serialVersionUID для захисту від несумісних змін. "
Кірбі

7
serialVersionUIDце НЕ а «унікальний ідентифікатор для кожного класу». Повнокваліфікована назва класу така. Це показник версії .
користувач207421

58

Оригінальне запитання запитувало "чому це важливо" та "приклад", де це Serial Version IDбуло б корисно. Ну я знайшов.

Скажіть, ви створюєте Carклас, інстанціюєте його і записуєте його в об'єктний потік. Згладжений автомобільний об'єкт деякий час сидить у файловій системі. Тим часом, якщо Carклас буде змінено, додавши нове поле. Пізніше, коли ви намагаєтесь прочитати (тобто десеріалізувати) сплющений Carоб’єкт, ви отримаєтеjava.io.InvalidClassException - тому що всі серіалізаційні класи автоматично отримують унікальний ідентифікатор. Цей виняток кидається, коли ідентифікатор класу не дорівнює ідентифікатору сплющеного об'єкта. Якщо ви по-справжньому задумаєтесь, виняток кидається через додавання нового поля. Ви можете уникнути відкидання цього винятку, контролюючи версію самостійно, оголосивши явний serialVersionUID. Існує також невелика перевага від ефективності в явній декларації вашогоserialVersionUID(тому що не треба обчислювати). Отже, найкраще додавати власні serialVersionUID до своїх серіалізаційних класів, як тільки ви створюєте їх, як показано нижче:

public class Car {
    static final long serialVersionUID = 1L; //assign a long value
}

Кожному UID слід призначити випадкове довге число, а не 1L.
абас

4
@abbas 'Один' повинен робити це чому? Поясніть, будь ласка, яку різницю це має.
користувач207421

Як видно з назви, він представляє версію класу, яка використовувалася для серіалізації об'єкта. Якщо ви кожного разу присвоюєте йому одне і те саме число, ви не зможете знайти потрібний клас для його десеріалізації. Тому щоразу, коли ви вносите зміни в класі, краще також змінити версію.
абас

2
@abbas, цей намір не суперечить використанню приростаючих натуральних чисел від 1тощо.
Вадим

2
@BillK, я вважав, що перевірка серіалізації пов'язана з парою імені класу та serialVersionUID. Тож різні схеми нумерації різних класів і бібліотек не можуть жодним чином втручатися. Або ви мали на увазі бібліотеки, що генерують код?
Вадим

44

Спершу мені потрібно пояснити, що таке серіалізація.

Серіалізація дозволяє перетворити об’єкт у потік для надсилання цього об’єкта по мережі АБО Зберегти у файл АБО зберегти в БД для використання листів.

Існують деякі правила серіалізації .

  • Об'єкт можна серіалізувати лише в тому випадку, якщо його клас або його надклас реалізує інтерфейс Serializable

  • Об'єкт є серіалізаційним (сам реалізує інтерфейс Serializable), навіть якщо його суперклас не є. Однак перший суперклас в ієрархії класу серіалізацій, який не реалізує інтерфейс Serializable, ОБОВ'ЯЗКОВО мати конструктор без аргументів. Якщо це буде порушено, readObject () створить java.io.InvalidClassException під час виконання

  • Всі примітивні типи є серіалізаційними.

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

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

Коли Objectсеріалізується, Java Runtime пов'язує номер серійної версії aka, the serialVersionID.

Де нам потрібен serialVersionID: Під час десеріалізації переконайтеся, що відправник та отримувач сумісні щодо серіалізації. Якщо одержувач завантажив клас з іншим,serialVersionIDдесеріалізація закінчитьсяInvalidClassCastException.
Клас, який може бути серіалізуючим, може заявити про своєserialVersionUIDявно, оголосивши ім'я поля,serialVersionUIDяке повинно бути статичним, остаточним та типовим.

Спробуємо це на прикладі.

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)

Виправте мене, якщо я помиляюся - місцевий клас - це той, який ви зараз використовуєте / використовуєте на classpath, а потоковий - той, який використовується іншою стороною (наприклад, сервер, який повертає вам відповідь і серіалізував відповідь). Ви можете зіткнутися з цією ситуацією, коли ви спілкуєтесь із сервером, який оновив свої сторонні зв'язки, але ви (клієнт) цього не робили.
Віктор

37

Якщо вам ніколи не доведеться серіалізувати свої об’єкти, щоб байтувати масив і відправляти / зберігати їх, тоді вам не потрібно буде турбуватися про це. Якщо ви це зробите, то ви повинні врахувати ваш serialVersionUID, оскільки десеріалізатор об’єкта буде відповідати його версії об'єкта, яку має його завантажувач класів. Детальніше про це читайте в специфікації мови Java.


10
Якщо ви не збираєтеся серіалізувати об'єкти, чому вони серіалізуються?
erickson

7
@erickson - батьківський клас може бути серіалізаційним, скажімо, ArrayList, але ви хочете, щоб ваш власний об’єкт (скажімо, список модифікованих масивів) використовував його як базу, але ніколи не збирається серіалізувати створену вами колекцію.
MetroidFan2002

5
Він не згадується ніде в специфікації мови Java. Це згадується в Специфікації версії об'єкта.
користувач207421

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's ExtensionFunctionDefinition), і в цьому випадку це рішення є нездійсненним. Я думаю, що ти маєш рацію, було б добре, якби попередження було проігноровано у певних випадках (наприклад, якщо ти
переходив

2
Безумовно, якщо ви додасте клас як член, а не успадковуєте його, вам доведеться написати метод обгортки для КОЖНОГО методу класу-члена, який ви хотіли використовувати, що зробить його нездійсненним у великій кількості ситуацій .. якщо тільки java не має функції, подібної до perl __AUTOLOAD, про яку я не знаю.
M_M

4
@M_M: Коли ви делегуєте безліч методів своєму обгорнутому об'єкту, звичайно, не було б доречним використання делегування. Але я припускаю, що цей випадок є ознакою помилки в дизайні - користувачам вашого класу (наприклад, "MainGui") не потрібно було б називати безліч методів загорнутого об'єкта (наприклад, JFrame).
Paŭlo Ebermann

1
Що мені не подобається в делегуванні - це необхідність мати посилання на делегата. І кожна довідка означає більше пам’яті. Виправте мене, якщо я помиляюся. Якщо мені потрібен CustomizedArrayList із 100 об’єктів, це не має великого значення, але якщо мені потрібні сотні CustomizdeArrayLists з декількох об’єктів, тоді використання пам'яті значно збільшується.
WVrock

2
Throwable можна серіалізувати, і лише Throwable піддається передачі, тому неможливо визначити виняток, який не піддається серіалізації. Делегація неможлива.
Філ

22

Щоб зрозуміти значення поля serialVersionUID, слід зрозуміти, як працює серіалізація / десеріалізація.

Коли об'єкт класу Serializable є серіалізованим, Java Runtime пов'язує номер серійної версії (називається serialVersionUID) з цим серіалізованим об'єктом. У той час, коли ви десериалізуєте цей серіалізований об’єкт, Java Runtime збігається з serialVersionUID серіалізованого об'єкта з serialVersionUID класу. Якщо обидва рівні, тоді лише він продовжується з подальшим процесом десеріалізації інакше кидає InvalidClassException.

Отже, ми робимо висновок, що для успішності процесу серіалізації / десеріалізації serialVersionUID серіалізованого об'єкта повинен бути еквівалентний serialVersionUID класу. У разі, якщо програміст явно в програмі вказує значення serialVersionUID, то те саме значення буде пов'язане з серіалізованим об'єктом і класом, незалежно від платформи серіалізації та деріаріалізації (наприклад, серіалізація може бути виконана на платформі, як Windows, за допомогою сонця або MS JVM і десеріалізація можуть бути на різних платформах Linux, використовуючи Zing JVM).

Але у випадку, якщо serialVersionUID не визначений програмістом, тоді, виконуючи серіалізацію \ DeSerialization будь-якого об'єкта, час виконання Java використовує власний алгоритм для його обчислення. Цей алгоритм обчислення serialVersionUID варіюється від одного JRE до іншого. Можливо також, що середовище, де об'єкт серіалізується, використовує один JRE (наприклад: SUN JVM), а середовище, де відбувається деріаріалізація, використовує Linux Jvm (zing). У таких випадках serialVersionUID, пов'язаний із серіалізованим об'єктом, буде відрізнятися від serialVersionUID класу, обчислений у середовищі десеріалізації. У свою чергу десеріалізація не буде успішною. Отже, щоб уникнути подібних ситуацій / проблем, програміст повинен завжди вказувати serialVersionUID класу Serializable.


2
Алгоритм не змінюється, але він дещо недоописаний.
користувач207421

... алгоритм не змінюється, але він дещо зазначається ... тобто будь-який jvm може змінюватися ..... @ user207421
AnthonyJClink

18

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


2
Сміття. Це адекватно в тому випадку, коли клас не змінився. У вас є нульові докази на підтвердження '99,9999% '.
користувач207421

18

Що стосується прикладу, коли відсутній serialVersionUID може спричинити проблеми:

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

Цей POJO'sклас упаковувався всередині банку EJB, а всередині його власного банку - у WEB-INF / lib веб-модуля. Вони насправді того ж класу, але коли я пакую модуль EJB, я розпаковую банку цього 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 різних версій або постачальники будуть взаємодіяти правильно або якщо вони зберігаються та отримуються поза VM, наприклад, HttpSessionдані сеансу можуть залишатися навіть під час перезавантаження та оновлення сервера додатків.

Для всіх інших випадків я використовую

@SuppressWarnings("serial")

оскільки більшість часу за замовчуванням serialVersionUIDдостатньо. Це включає в себе Exception, HttpServlet.


Він не включає HttpServlet в контейнери, де вони можуть бути замінені, або виняток, наприклад, у RMI.
користувач207421

14

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

Якщо ви не хочете, щоб це попередження надійшло, скористайтеся цим:

@SuppressWarnings("serial")

11

Було б добре, якби CheckStyle міг переконатися, що serialVersionUID для класу, який реалізує Serializable, має хороше значення, тобто, що він відповідає тому, що виробляв би генератор ідентифікаторів серійної версії. Наприклад, якщо у вас є проект з великою кількістю серіалізаційних DTO, наприклад, пам'ятаючи видалити існуючий serialVersionUID і відновити його - це біль, і наразі єдиний спосіб (про який я знаю) перевірити це - регенерувати для кожного класу і порівнювати з старий. Це дуже дуже боляче.


8
Якщо ви встановлюєте serialVersionUID завжди на те саме значення, яке виробляв би генератор, вам це зовсім не потрібно. Зрештою, його причиною є залишатися однаковою після змін, коли клас ще сумісний.
Paŭlo Ebermann

4
Причина в тому, що різні компілятори придумують однакове значення для одного класу. Як пояснено в javadocs (також відповів вище), генерована версія є крихкою і може змінюватися навіть тоді, коли клас належним чином дезариалізується. Поки ви кожного разу виконуєте цей тест на одному і тому ж компіляторі, він повинен бути безпечним. Бог допоможе вам, якщо ви оновите jdk, і з’явиться нове правило, навіть якщо код не змінився.
Ендрю Бекер

2
Це не потрібно, щоб відповідати тому, що serialverбуде виробляти. -1
користувач207421

Взагалі це взагалі не потрібно. @ Випадок 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

Код: javassist.SerialVersionUID


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 / бін знаходиться в ПАТ.


2
Дає мені ідею: завжди регенеруйте серійну версію uid за допомогою такого інструменту, ніколи вручну, перед тим, як зробити реліз - таким чином, ви не забудете внести зміни до класу, чиї серійні версії повинні змінитися через фактичну несумісна зміна. Відстежувати це вручну дуже важко.
Ігнаціо

6

Це питання дуже добре зафіксовано в «Ефективній Java» Джошуа Блоха. Дуже гарна книга і обов’язково прочитана. Я наведу кілька причин нижче:

Виконання серіалізації створює число, яке називається Послідовна версія для кожного класу серіалізації. Це число називається serialVersionUID. Тепер за цим числом стоїть деяка математика, і вона виходить на основі полів / методів, визначених у класі. Для одного і того ж класу щоразу створюється одна і та ж версія. Цей номер використовується під час десеріалізації для перевірки того, що відправник і отримувач серіалізованого об'єкта завантажують класи для цього об’єкта, сумісні щодо серіалізації. Якщо одержувач завантажив клас для об'єкта, який має інший serialVersionUID, ніж клас відповідного класу відправника, то десеріалізація призведе до InvalidClassException.

Якщо клас є серіалізаційним, ви можете також оголосити власний serialVersionUID явно, оголосивши поле під назвою "serialVersionUID", яке повинно бути статичним, остаточним і тривалим. Більшість IDE, як Eclipse, допоможуть вам створити цю довгу нитку.


6

Кожен раз, коли об'єкт серіалізується, об'єкт штампується ідентифікаційним номером версії для класу об'єкта. Цей ідентифікатор називається serialVersionUID, і він обчислюється на основі інформації про структуру класу. Припустимо, ви створили клас Employee, і він має ідентифікатор версії № 333 (призначений JVM). Тепер, коли ви будете серіалізувати об’єкт цього класу (Припустимо, співробітник), JVM призначить його UID як # 333.

Розглянемо ситуацію - у майбутньому вам потрібно буде редагувати або змінювати клас, і в тому випадку, коли ви його модифікуєте, JVM призначить йому новий UID (припустимо, № 444). Тепер, коли ви намагаєтеся десериалізувати об'єкт співробітника, JVM порівнює ідентифікатор версії серіалізованого об’єкта (Співробітник) (№ 333) з типом класу, тобто # 444 (оскільки він був змінений). Для порівняння, JVM знайде, що обидві версії UID відрізняються, і, отже, десеріалізація не вдасться. Отже, якщо serialVersionID для кожного класу визначається самим програмістом. Це буде таким же, навіть якщо клас розвиватиметься в майбутньому, і тому JVM завжди знайде, що клас сумісний із серіалізованим об'єктом, навіть незважаючи на те, що клас змінено. Для отримання додаткової інформації ви можете ознайомитись у розділі 14 HEAD FIRST JAVA.


1
Кожен раз, коли клас об'єкта серіалізується serialVersionUID, передається. Він не надсилається з кожним об'єктом.
користувач207421

3

Просте пояснення:

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

    Серіалізація - це в основному запис даних класу у файл / потік / тощо. Десеріалізація - це зчитування цих даних назад до класу.

  2. Ви маєте намір зайнятися виробництвом?

    Якщо ви просто тестуєте щось з неважливими / фальшивими даними, тоді не хвилюйтеся про це (якщо ви не тестуєте серіалізацію безпосередньо).

  3. Це перша версія?

    Якщо так, встановіть serialVersionUID=1L.

  4. Це друга, третя і т. П. Версія?

    Тепер вам потрібно переживати serialVersionUID, і слід заглянути в це глибоко.

В основному, якщо ви не оновлюєте версію правильно під час оновлення класу, який потрібно писати / читати, ви отримаєте помилку при спробі читання старих даних.


2

'serialVersionUID' - це 64-бітове число, яке використовується для унікальної ідентифікації класу в процесі десеріалізації. Коли ви серіалізуєте об'єкт, serialVersionUID класу також записується у файл. Щоразу, коли ви десериалізуєте цей об'єкт, час запуску Java витягнути це значення serialVersionUID з серіалізованих даних і порівняйте те саме значення, що асоціюється з класом. Якщо обидва не збігаються, буде викинуто "java.io.InvalidClassException".

Якщо клас серіалізації не явно декларує serialVersionUID, то час виконання серіалізації обчислює значення serialVersionUID для цього класу на основі різних аспектів класу, таких як поля, методи тощо. ,, Ви можете посилатися на це посилання для демонстраційного застосування.


1

Якщо коротко розповісти, це поле використовується для перевірки, чи можна деріаріалізувати серіалізовані дані правильно. Серіалізація та десеріалізація часто робляться різними копіями програми - наприклад, сервер перетворює об'єкт у рядок, а клієнт перетворює отриману рядок в об'єкт. Це поле говорить про те, що обидва діють з однаковим уявленням про те, що це за об'єкт. Це поле допомагає, коли:

  • у вас є багато різних копій програми в різних місцях (наприклад, 1 сервер і 100 клієнтів). Якщо ви зміните свій об'єкт, зміните номер своєї версії та забудете оновити одного цього клієнта, він буде знати, що він не здатний до дезаріалізації

  • ви зберегли свої дані в якомусь файлі, а пізніше ви намагаєтесь відкрити їх оновленою версією програми з модифікованим об'єктом - ви будете знати, що цей файл не сумісний, якщо ви будете тримати версію правильно

Коли це важливо?

Найбільш очевидно - якщо ви додасте кілька полів до свого об'єкта, старіші версії не зможуть їх використовувати, оскільки вони не мають цих полів у своїй об’єктній структурі.

Менш очевидно - Коли ви дезаріалізуєте об’єкт, поля, які відсутні в рядку, будуть зберігатися як NULL. Якщо ви видалили поле зі свого об’єкта, старіші версії зберігатимуть це поле як завжди-NULL, що може призвести до неправильної поведінки, якщо старіші версії покладаються на дані в цьому полі (все одно ви створили його для чогось, а не просто для розваги :-))

Найменш очевидно - Іноді ви змінюєте думку, яку ви вкладаєте в якесь значення поля. Наприклад, коли вам 12 років, ви маєте на увазі "велосипед" під "велосипедом", але коли вам 18 років, ви маєте на увазі "мотоцикл" - якщо ваші друзі запросять вас на "прогулянку на велосипеді по місту", і ви будете єдиним, хто приїхавши на велосипеді, ви не зупините, як важливо зберігати однакове значення в полях :-)


1

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

Серіалізація: ми часто працюємо з важливими об'єктами, стан яких (дані у змінних об'єкта) настільки важливі, що ми не можемо ризикувати втратити його через збої в електроенергії / системі (або) відмови мережі в разі пересилання стану об'єкта в інший машина. Рішення цієї проблеми називається "Наполегливість", що просто означає збереження (зберігання / збереження) даних. Серіалізація - це один із багатьох інших способів досягнення стійкості (шляхом збереження даних на диск / пам'ять). Зберігаючи стан об'єкта, важливо створити ідентичність об’єкта, вміти його правильно читати назад (де-серіалізація). Цей унікальний ідентифікатор - ідентифікатор - SerialVersionUID.


0

Що таке SerialVersionUID? Відповідь: Скажімо, що є дві людини, одна з штаб-квартири, а друга з ODC, обидві збираються виконати серіалізацію та десеріалізацію відповідно. У цьому випадку для підтвердження того, що приймач, який знаходиться в ODC, є автентифікованою особою, JVM створює унікальний ідентифікатор, який відомий як SerialVersionUID.

Ось приємне пояснення на основі сценарію,

Чому SerialVersionUID?

Серіалізація : Під час серіалізації на кожному стороні відправника об'єкта JVM збереже унікальний ідентифікатор. JVM несе відповідальність за генерування цього унікального ідентифікатора на основі відповідного файлу .class, який присутній у системі відправника.

Десеріалізація : Під час десеріалізації приймач JVM зіставить унікальний ідентифікатор, пов'язаний з Об'єктом, з локальним класом Unique ID, тобто JVM також створить унікальний ідентифікатор на основі відповідного файлу .class, який присутній у системі приймача. Якщо обидва унікальні ідентифікатори збігаються, буде виконуватися лише десеріалізація. В іншому випадку ми отримаємо Виняток із виконання програми, кажучи InvalidClassException. Цей унікальний ідентифікатор - це не що інше, як SerialVersionUID

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.