1. transient haqqında daha ətraflı
Java-da transient açar sözü — serializatora belə deməyin yoludur: “Zəhmət olmasa, bu sahəyə toxunma, obyekti saxlayarkən onu nəzərə alma!”. Əgər sahəni transient kimi elan etsəniz, o, serializə olunmuş bayt axınına düşməyəcək. Bu, xüsusilə həssas məlumatlar (məsələn, şifrələr) və ya saxlanmasına ehtiyac olmayan müvəqqəti hesablamalar üçün faydalıdır.
Nümunə: nə üçün transient lazımdır?
Tutaq ki, bizdə istifadəçi sinfi var:
import java.io.Serializable;
public class User implements Serializable {
private String username;
private transient String password; // Şifrəni saxlamaq istəmirik!
public User(String username, String password) {
this.username = username;
this.password = password;
}
// Burada getter və setter-lər var
}
Əgər bu sinifin obyektini serializasiya etsək, password sahəsi fayla (və ya başqa axına) düşməyəcək. Bu o deməkdir ki, deserializasiya zamanı parol defolt dəyərə bərabər olacaq — obyektlər üçün bu null, ədədlər üçün — 0, boolean üçün — false.
Bu praktikada necə işləyir?
Gəlin kiçik bir eksperiment aparaq. Əvvəlcə istifadəçini serializasiya edək:
import java.io.*;
public class TransientDemo {
public static void main(String[] args) throws Exception {
User user = new User("vasya", "qwerty123");
// Obyekti fayla saxlayırıq
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.ser"));
out.writeObject(user);
out.close();
// İndi obyekti geri oxuyuruq
ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.ser"));
User restored = (User) in.readObject();
in.close();
System.out.println("Username: " + restored.username);
System.out.println("Password: " + restored.password);
}
}
Nəticə:
Username: vasya
Password: null
Gördüyünüz kimi, password sahəsi bərpa olunmadı — o, transient-dir, yəni serializator onu görməzlikdən gəldi.
transient-i harada və nə üçün istifadə etməli?
- Şifrələr və token-lər. Onları heç vaxt serializasiya etməyin!
- Keşlənmiş və ya müvəqqəti məlumatlar. Məsələn, yerində/anında hesablana bilən bir sahəniz varsa.
- Serializasiya etmək mümkün olmayan və ya gərək olmayan obyektlər. Məsələn, verilənlər bazası bağlantılarına istinadlar, axınlar, socket-lər.
transient sahələrin davranış xüsusiyyətləri
Obyekt deserializasiya ediləndə, transient kimi işarələnmiş bütün sahələr defolt dəyərlərini alır. Əgər onlara yenidən məna vermək lazımdırsa, readObject metodundan istifadə edib onları əl ilə doldura bilərsiniz (keşi yenidən hesablamaq, istifadəçidən parolu istəmək və s.).
2. serialVersionUID: sinif versiyasının unikal identifikatoru
serialVersionUID — bu, serializasiya olunan sinfin “versiyasını” müəyyən edən xüsusi statik long tipli sahədir. Serializasiya zamanı serialVersionUID dəyəri yazılır, deserializasiya zamanı JVM onu cari sinifdəki dəyərlə müqayisə edir. Əgər onlar uyğun gəlməsə — istisna atılacaq və obyekt bərpa olunmayacaq.
serialVersionUID necə elan olunur?
Çox sadədir:
private static final long serialVersionUID = 1L;
Adətən onu məhz Serializable-ı həyata keçirən sinifdə elan edirlər:
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// ... qalan sahələr və metodlar
}
serialVersionUID nə üçün lazımdır?
Təsəvvür edin ki, sinfin obyektini fayla saxladınız, sonra isə sinfin strukturunu dəyişdirdiniz (sahə əlavə etdiniz, nəyisə yenidən adlandırdınız və s.). Əgər serialVersionUID fərqlənirsə, JVM sinfi köhnə versiya ilə uyğun deyil hesab edir və obyektin deserializasiya olunmasına imkan vermir. Bu, gözlənilməz xətaların qarşısını alır.
serialVersionUID elan olunmasa, nə baş verəcək?
Əgər serialVersionUID-i açıq şəkildə elan etməsəniz, kompilyator onu sinfin strukturuna əsasən özü generasiya edəcək. Amma kiçik bir dəyişiklik belə (məsələn, sahə əlavə etmək və ya silmək) serialVersionUID-in dəyişməsinə səbəb olacaq. Nəticədə, sinfin köhnə versiyası ilə saxlanmış obyektləri deserializasiya edə bilməyəcəksiniz.
Bu səbəbdən serialVersionUID-i həmişə açıq şəkildə təyin etmək tövsiyə olunur!
Nümayiş: serialVersionUID uyğunsuzluğu
1) Əvvəlcə sinfi yaradaq və obyekti serializasiya edək:
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
public User(String username) {
this.username = username;
}
}
2) Sonra serialVersionUID-i dəyişək:
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 2L; // Əvvəl 1L idi, indi 2L!
private String username;
public User(String username) {
this.username = username;
}
}
Nəticə:
java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
JVM dürüst xəbərdar edir: “Versiyalar uyğun gəlmir!”
serialVersionUID üçün hansı dəyəri seçmək lazımdır?
Ən çox sadə dəyərlərdən istifadə olunur (1L, 2L, 42L), böyük layihlərdə isə IDE “uzun” dəyərlər generasiya edir. Əsas odur ki, onu yalnız sinifin strukturu uyğunsuz şəkildə dəyişəndə dəyişəsiniz.
3. Təcrübə: transient sahələr və serialVersionUID iş başında
Nümunə: transient sahəsi olan sinif
Gəlin tədris tətbiqini (məsələn, kontakt meneceri) dəyişdirək və istifadəçi sinfinə serializasiyaya düşməməli olan müvəqqəti avtorizasiya token-i üçün sahə əlavə edək.
import java.io.Serializable;
public class Contact implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String phone;
private transient String sessionToken; // müvəqqəti token
public Contact(String name, String phone, String sessionToken) {
this.name = name;
this.phone = phone;
this.sessionToken = sessionToken;
}
@Override
public String toString() {
return "Contact{" +
"name='" + name + '\'' +
", phone='" + phone + '\'' +
", sessionToken='" + sessionToken + '\'' +
'}';
}
}
İndi obyekti serializasiya və deserializasiya etməyə cəhd edək:
import java.io.*;
public class TransientAndSUIDDemo {
public static void main(String[] args) throws Exception {
Contact c = new Contact("İvan", "+19990001122", "token-12345");
// Obyekti saxlayırıq
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("contact.ser"));
out.writeObject(c);
out.close();
// Obyekti bərpa edirik
ObjectInputStream in = new ObjectInputStream(new FileInputStream("contact.ser"));
Contact restored = (Contact) in.readObject();
in.close();
System.out.println("Serializasiyadan əvvəl: " + c);
System.out.println("Deserializasiyadan sonra: " + restored);
}
}
Çıxış:
Serializasiyadan əvvəl: Contact{name='İvan', phone='+19990001122', sessionToken='token-12345'}
Deserializasiyadan sonra: Contact{name='İvan', phone='+19990001122', sessionToken='null'}
Gördüyünüz kimi, sessionToken sahəsi bərpa olunmadı — o, transient-dir.
Nümunə: serialVersionUID ilə eksperiment
1) Əvvəlcə serialVersionUID = 1L olan obyekti serializasiya edirik.
2) Sonra serialVersionUID-i 2L edirik və eyni faylı deserializasiya etməyə çalışırıq.
Nəticə: yuxarıda göstərildiyi kimi InvalidClassException alacaqsınız.
4. Niyə açıq şəkildə serialVersionUID təyin etmək daha yaxşıdır?
- Açıq — qeyri-açıqdansa daha yaxşıdır. Uyğunluğu siz idarə edirsiniz: sinifin strukturu kritik şəkildə dəyişməyibsə, köhnə serialVersionUID-i saxlayırsınız və obyektlər problemsiz deserializasiya olunur.
- Avtomatik generasiya təhlükəlidir. İstənilən dəyişiklik hesablanmış dəyəri dəyişdirə və saxlanmış verilənlərlə uyğunluğu “sındıra” bilər.
- IDE kömək edir. IDE-lərin əksəriyyəti (məsələn, IntelliJ IDEA) serialVersionUID-i avtomatik generasiya edə bilir.
5. transient və serialVersionUID ilə işləyərkən tipik səhvlər
Səhv №1: həssas sahəni transient kimi işarələməyi unutmaq.
Nəticədə şifrələr və ya token-lər təsadüfən serializə olunmuş fayllara düşür. Bu, təkcə xoşagəlməz deyil, həm də təhlükəlidir.
Səhv №2: serialVersionUID-i açıq şəkildə elan etməmək.
Sinif dəyişdirildi və indi köhnə obyektləri deserializasiya etmək mümkün deyil: JVM onları uyğunsuz sayır, halbuki mahiyyətcə struktur kritik şəkildə dəyişməmiş ola bilər.
Səhv №3: serialVersionUID-i ehtiyac olmadan dəyişmək.
Əgər sadəcə getter əlavə etmisinizsə və ya şərh dəyişmisinizsə, serialVersionUID-i dəyişməyə ehtiyac yoxdur — əks halda köhnə məlumatlar deserializasiya olunmayacaq.
Səhv №4: serialVersionUID static və ya final deyil.
Sahə belə elan edilməlidir: private static final long serialVersionUID. Əks halda JVM onu düzgün qəbul etməyəcək.
Səhv №5: deserializasiyadan sonra transient sahəni bərpa etməmək.
Əgər dəyər obyektin işi üçün kritikdirsə, onu readObject-də bərpa edin — əks halda obyekt səhv işləyə bilər.
GO TO FULL VERSION