1. Uyğunluq problemi
Təsəvvür edin: tətbiqinizin ilk versiyasını buraxdınız, istifadəçilər məlumatları saxlamağa başladılar (məsələn, istifadəçi profilləri və ya sazlamalar). Bir aydan sonra başa düşdünüz ki, UserProfile sinfində email sahəsi çatışmır və onu əlavə etdiniz. Hər şey əladır... ta ki, köhnə faylı yükləməyə çalışana qədər. Ən yaxşı halda yeni sahə boş qalacaq, ən pis halda — istisna alıb istifadəçini məyus edəcəksiniz.
Seriyalaşdırma uyğunluğu — proqramın əvvəlki sinif versiyaları ilə seriyalaşdırılmış məlumatları düzgün oxuya bilməsi və əksinə yazması deməkdir. Java-da (xüsusən Serializable vasitəsilə binar seriyalaşdırmada) bu mövzu xüsusilə vacibdir, çünki JVM siniflərin struktur dəyişikliyinə qarşı çox həssasdır.
Problemin yarandığı tipik ssenarilər:
- Sinfə yeni sahə əlavə etdiniz.
- Köhnə sahəni sildiniz.
- Sahənin tipini dəyişdiniz (məsələn, int → String).
- Sinfə ad dəyişikliyi etdiniz və ya onu başqa paketə köçürdünüz.
- Obyektləri seriyalaşdıran kitabxananı və ya framework-ü yenilədiniz.
Bu halların hamısında köhnə seriyalaşdırılmış məlumatlar yeni versiya üçün “oxunmayan” ola bilər.
2. serialVersionUID: seriyalaşdırılan sinfin “pasportu”
Java-da hər bir seriyalaşdırılan sinif (yəni Serializable interfeysini həyata keçirən) unikal versiya identifikatoruna — serialVersionUID — malikdir. Bu sahədən JVM obyektin həmin siniflə deserializasiya olunub-olunmayacağını yoxlamaq üçün istifadə edir. İdentifikatorlar uyğun gəlmirsə — InvalidClassException alırıq.
private static final long serialVersionUID = 1L;
Əgər bu sahəni açıq şəkildə elan etməmisinizsə, Java onu sinfin strukturu (sahələr, metodlar, modifikatorlar və s.) əsasında avtomatik generasiya edəcək. Amma sonradan sinfi (hətta cüzi də olsa) dəyişsəniz, avtomatik yaradılmış serialVersionUID dəyişəcək və köhnə məlumatlar uyğunsuz olacaq.
Yoxlama necə işləyir?
Obyekt seriyalaşdırılarkən, onun məlumatları ilə birlikdə serialVersionUID dəyəri də axına yazılır. Deserializasiya zamanı JVM bu identifikatoru cari sinifdə göstərilənlə tutuşdurur. Hər şey uyğun gəlirsə — obyekt sakitcə bərpa olunur. İdentifikatorlar fərqlidirsə, proses dərhal xəta ilə dayandırılır: JVM hesab edir ki, sinif o qədər dəyişib ki, köhnə məlumatlar ona artıq uyğun deyil.
serialVersionUID-i nə üçün açıq şəkildə elan etməli?
Əgər serialVersionUID dəyərini özünüz təyin edirsinizsə, sinifdə hansı dəyişikliklərin “məqbul” sayılacağını nəzarətdə saxlayırsınız. Məsələn, yeni sahə əlavə etmisiniz, amma köhnə obyektlərin hələ də yüklənməsini istəyirsiniz? İdentifikatoru əvvəlki kimi saxlayın — və deserializasiya problemsiz keçəcək. Avtomatik generasiyaya güvənsəniz, xoşagəlməz sürpriz ola bilər: koddakı ən kiçik dəyişiklik belə serialVersionUID-in dəyişməsinə və nəticədə köhnə saxlamaların açılmamasına gətirib çıxaracaq.
Nümunə:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// ... getter və setter-lər
}
Artıq yeni sahələr (məcburi deyilsə) əlavə edə bilərsiniz və köhnə obyektlərin deserializasiyası pozulmayacaq.
3. Sinif dəyişdikdə nə baş verir?
Yeni sahələrin əlavə edilməsi
Köhnə seriyalaşdırılmış obyekt → əlavə sahəsi olan yeni sinif
- Yeni sahə defolt dəyər alacaq (null, 0, false).
- Qalan hər şey düzgün deserializasiya olunacaq.
Nümunə:
// Əvvəl:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
}
// Sonra:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String email; // yeni sahə
}
Nəticə: Köhnə obyektlər yüklənir, email == null.
Sahənin silinməsi
Köhnə seriyalaşdırılmış obyektdə sahə var, amma yeni sinifdə yoxdur
- Bu sahə deserializasiya zamanı sadəcə nəzərə alınmır.
- Əsas odur — serialVersionUID-i dəyişməyəsiniz.
Sahənin tipinin dəyişdirilməsi
Məsələn, əvvəl int age idi, indi String age oldu.
- Bu, uyğunsuz dəyişiklikdir. Deserializasiya cəhdində xəta yaranacaq (adətən InvalidClassException və ya ClassCastException).
- Belə dəyişikliklərdən qaçın və ya uyğunluğu fərdi seriyalaşdırma ilə təmin edin (aşağıya baxın).
Sinfin və ya paketin adının dəyişdirilməsi
Burada vəziyyət sərtdir: sinfin və ya paketin adını dəyişsəniz, deserializasiya baş tutmayacaq. Seriyalaşdırılmış axında sinfin tam adı saxlanılır və JVM məhz onu görməyi gözləyir. Ona görə istənilən ad dəyişikliyi kritik dəyişiklik sayılır. Layihə strukturunu dəyişmək lazımdırsa, əl ilə məlumat miqrasiyasından yan keçmək olmur.
4. transient və static: nə seriyalaşdırılır, nə isə yox?
- static sahələr ümumiyyətlə seriyalaşdırılmır — onlar sinfə məxsusdur, obyektə yox.
- transient sahələr müvəqqəti məlumatları işarələyir və seriyalaşdırmaya düşməməlidir (məsələn, keş, müvəqqəti tokenlər).
Nümunə:
public class Session implements Serializable {
private static final long serialVersionUID = 1L;
private String user;
private transient String sessionToken; // seriyalaşdırılmır
}
Deserializasiya zamanı sessionToken null olacaq, hətta seriyalaşdırmadan əvvəl obyekt daxilində o doldurulmuş olsa belə.
5. Fərdi seriyalaşdırma: writeObject/readObject
Əgər daha mürəkkəb uyğunluq məntiqinə ehtiyacınız varsa (məsələn, köhnə sahələri yenilərinə çevirmək, dəyişmiş tipləri işləmək), xüsusi metodlar reallaşdıra bilərsiniz:
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// Lazım gələrsə əlavə məntiq
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Əlavə məntiq, məsələn, yeni sahəni köhnələrin əsasında doldurmaq
}
Təkamül nümunəsi:
public class User implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
private int age; // əvvəllər String birthYear idi
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Əgər birthYear sahəsi mövcud idisə, onu age-ə çevirmək
// (əgər birthYear-ı transient kimi saxlayırsınızsa, kod nümunəsi)
}
}
6. XML və JSON-da uyğunluq: mətn formatlarının elastikliyi
Binar seriyalaşdırmadan fərqli olaraq, XML və JSON formatları sinif strukturundakı dəyişikliklərə xeyli dözümlüdür.
XML (JAXB) və JSON (Jackson, Gson)
Binar seriyalaşdırmadan fərqli olaraq, XML və ya JSON ilə işləyərkən deserializasiya xeyli yumşaq davranır. Əgər məlumatlarda sinfinizdə olmayan sahə rast gəlinərsə, o sadəcə ignor edilir. Sinfinizdə olan, amma mənbə məlumatlarında olmayan yeni sahələr isə defolt dəyərlər alır — adətən obyektlər üçün null və ya ədədlər üçün 0. Elementlərin ardıcıllığı əhəmiyyət daşımır, beləliklə teqləri və ya açarları yerini dəyişsəniz də, hər şey düzgün parse olunacaq.
Annotasiyalar tam nəzarət verir: faylda hansı adın istifadə olunacağını, hansı sahələrin məcburi, hansının isə ötürülə biləcəyini göstərə, hətta formatlaşdırmanı qura bilərsiniz. Məsələn, JAXB-də User sinfi belə görünə bilər:
public class User {
@XmlElement(required = true)
private String name;
@XmlElement
private String email; // yeni sahə, məcburi deyil
}
JSON üçün Jackson və ya Gson ilə təxminən belə:
public class User {
@JsonProperty("name")
private String name;
@JsonProperty("email")
private String email; // yeni sahə
}
Nəticə xoşdur: köhnə JSON və ya XML-fayllar sakitcə yüklənir, yeni sahələr sadəcə null alır, məlumatdakı artıq sahələr isə nəzərə alınmır. Sinfin strukturunu rahatlıqla dəyişə bilərsiniz, köhnə saxlamaları sındırmaqdan qorxmadan.
Nə vaxt əlavə nəzarət lazımdır?
Nəzarət xüsusən sahəni məcburi etdiyiniz zaman vacibdir. Əgər köhnə məlumatlarda bu sahə yoxdursa, deserializasiya xəta verəcək. Tip dəyişikliklərinə də eyni cür aiddir: əgər əvvəl sahə sətir idisə, siz onu ədədə çevirmisinizsə, köhnə məlumatlar parse olunmaya bilər. Buna görə belə dəyişikliklərdən əvvəl onların mövcud saxlamalara necə təsir edəcəyini yoxlamaq və zərurət olduqda miqrasiya hazırlamaq və ya defolt dəyərlər təyin etmək lazımdır.
7. Uyğunluğu təmin etmə strategiyaları
- Mütləq açıq şəkildə elan edin serialVersionUID. Bu, binar seriyalaşdırma üçün uyğunluğa nəzarətin əsas yoludur.
- Yalnız məcburi olmayan sahələr əlavə edin. Yeni sahələr ya null olmalı, ya da defolt dəyərə malik olmalıdır.
- İstifadə edin transient müvəqqəti və ya önəmsiz məlumatlar üçün. Belə sahələr seriyalaşdırmaya düşməyəcək və sinfin təkamülü zamanı problem yaratmayacaq.
- Dəyişiklikləri sənədləşdirin. Sinifin şərhində hansı sahələrin əlavə/silinməsi və bunun hansı versiyadan etibarən olduğu qeyd edilsin.
- Mürəkkəb hallarda — writeObject/readObject. Məlumatların “uçuşda” miqrasiyasını həyata keçirməyə imkan verir.
- Sxemlərdən istifadə edin (XML Schema, JSON Schema) kritik məlumatlar üçün. Bu, məlumat strukturunu açıq təsvir etməyə və yükləmə zamanı onu yoxlamağa kömək edir.
8. Təcrübə: uyğunsuzluq və təkamülün nümayişi
Uyğun gəlməyən serialVersionUID halında xəta nümayişi
// Əvvəlcə sinfin bir versiyası ilə obyekti seriyalaşdırırıq
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
}
// Sonra serialVersionUID-i dəyişirik (məsələn, 2L), kompilyasiya edirik və köhnə faylı yükləməyə cəhd edirik
public class User implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
}
Nəticə:
java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
Sinfin uğurlu təkamülü nümunəsi
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
// yeni sahə
private String email;
}
Əgər köhnə obyekti ( email olmadan) seriyalaşdırıb sonra sahəni əlavə etsəniz və serialVersionUID-i dəyişməsəniz, deserializasiya işləyəcək, email null olacaq.
9. Seriyalaşdırma uyğunluğu ilə işləyərkən tipik səhvlər
Səhv №1: serialVersionUID elan olunmayıb. Əgər serialVersionUID-i açıq şəkildə elan etməsəniz, JVM onu avtomatik generasiya edəcək. Sinfin ən kiçik dəyişiklikləri (məsələn, yeni metod əlavə etdiniz və ya sahənin modifikatorunu dəyişdiniz) serialVersionUID-in dəyişməsinə və nəticədə köhnə məlumatların deserializasiya olunmamasına gətirib çıxaracaq. Bu, backward compatibility-ni “sındırmağın” klassik yoludur.
Səhv №2: Sahənin tipini dəyişmək. Sahənin tipini dəyişdiniz (məsələn, int-dən String-ə) — istisna və ya qeyri-düzgün məlumat alacaqsınız. Belə dəyişikliklər xüsusi ehtiyat tələb edir, ən yaxşısı — əl miqrasiyası ilə writeObject/readObject.
Səhv №3: Sinfin/paketin silinməsi və ya adının dəyişdirilməsi. Sinfin adını dəyişmək və ya paketi dəyişmək köhnə obyektlərin deserializasiya olunmamasına gətirib çıxarır. Sinfin adı və paket seriyalaşdırılmış axında saxlanılır və JVM onları uyğunlaşdıra bilməyəcək.
Səhv №4: transient-dən sui-istifadə. Əgər vacib sahəni transient etsəniz (məsələn, istifadəçi id-si), o seriyalaşdırılmayacaq və obyekt bərpa olunanda dəyər itəcək.
Səhv №5: Kolleksiyaların uyğunsuz dəyişdirilməsi. Yeni kolleksiya-sahə əlavə etdiniz və ya kolleksiyanın tipini dəyişdiniz (məsələn, List-dən Set-ə) — köhnə məlumatlar səhv deserializasiya oluna və ya xəta yarada bilər.
Səhv №6: XML/JSON-da həddən artıq sərt məhdudiyyətlər. Əgər XML/JSON-sxemdə sahəni məcburi (required = true) kimi işarələsəniz, amma köhnə məlumatlarda o yoxdursa, yükləmə xəta ilə bitəcək. Annotasiyalar və sxemlərlə diqqətli olun!
GO TO FULL VERSION