CodeGym /Kurslar /JAVA 25 SELF /transient sahələr, serialVersionUID

transient sahələr, serialVersionUID

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

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. transientserialVersionUID 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.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Şəxsi Məlumatlar üçün Zaman Damgası: Varlığın Versiyalaşdırılması
Şəxsi Məlumatlar üçün Zaman Damgası: Varlığın Versiyalaşdırılması
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Maaş Sirləri: Müvəqqəti itmə və standart üzrə bərpa
Maaş Sirləri: Müvəqqəti itmə və standart üzrə bərpa
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION