1. writeReplace və readResolve metodları: nəzəriyyə
Bəzən standart serializasiya vasitələri kifayət etmir. Təsəvvür edin: sizdə singleton var (bütün proqramda yalnız bir nümunəsi ola bilən sinif) və istəyirsiniz ki, deserializasiyadan sonra da o yeganə nümunə olaraq qalsın (yeni klon yaranmasın). Yaxud da obyekti deyil, onun “yüngülləşdirilmiş” versiyasını (proksi) serializasiya etmək istəyirsiniz ki, reallaşdırma detallarını gizlədəsiniz və ya yerə qənaət edəsiniz.
Java-da bunun üçün xüsusi metodlar var: writeReplace və readResolve. Onların vəzifəsi — serializasiya və ya deserializasiya olunan obyekti başqa bir obyektlə əvəz etməkdir.
Sadə analogiya:
Sanki dostunuza bağlama göndərirsiniz, amma özünüzün yerinə qutuya oyuncaq dublikatınızı qoyursunuz. Dostunuz bağlamanı açanda isə oyuncaq əvəzinə onun əlində siz — həqiqi siz olursunuz! (Reallıqda bu belə işləmir, amma Java-da — tam mümkündür.)
writeReplace
private Object writeReplace() metodu obyekt serializasiya olunmazdan əvvəl çağırılır. O, əslində serializasiya olunacaq istənilən obyekti qaytara bilər. Əgər reallaşdırılmayıbsa — elə həmin obyekt serializasiya olunur.
İmza:
private Object writeReplace() throws ObjectStreamException
readResolve
private Object readResolve() metodu obyekt deserializasiya olunduqdan sonra çağırılır. O, yenicə yaradılmış obyekti başqa obyektlə əvəz etməyə imkan verir (məsələn, singletonu və ya keşlənmiş nümunəni qaytarmaq üçün).
İmza:
private Object readResolve() throws ObjectStreamException
Vacibdir:
Hər iki metod private olmalı və Object qaytarmalıdır. Bu, Java serializasiya spesifikasiyasının tələbidir. Əgər onları public etsəniz — serializasiya sadəcə onları nəzərə almayacaq.
2. writeReplace və readResolve-in praktik tətbiqi
Singleton və readResolve
Singleton — bütün proqramda yalnız bir nümunəsi ola bilən sinifdir. Belə obyekti serializasiya edib sonra bərpa etsəniz, readResolve olmadan yeni bir nümunə yaranacaq və “yeganəlik” qaydası pozulacaq. readResolve ilə isə məhz həmin obyekt qaytarıla bilər və singleton ideyası qorunur.
import java.io.*;
public class MySingleton implements Serializable {
private static final MySingleton INSTANCE = new MySingleton();
private MySingleton() {}
public static MySingleton getInstance() {
return INSTANCE;
}
// Deserializasiyadan sonra məhz INSTANCE qaytarılacağını təmin edirik
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}
İzah:
readResolve olmadan deserializasiyadan sonra orijinal singletona == üzrə bərabər olmayan yeni obyekt yaranacaq. readResolve ilə isə həmişə INSTANCE qaytarılır.
Gəlin praktikada yoxlayaq:
MySingleton s1 = MySingleton.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.bin"));
out.writeObject(s1);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.bin"));
MySingleton s2 = (MySingleton) in.readObject();
in.close();
System.out.println(s1 == s2); // readResolve olduqda true; onsuz — false
writeReplace: proksi obyektin serializasiyası
Bəzən obyekt serializasiya üçün çox “ağır” olur, yaxud həssas məlumatlar saxlayır, ya da sadəcə tam formada xaricə çıxmamalıdır. Bu halda “əvəzedici” — proksi obyekt serializasiya oluna bilər.
Nümunə:
Tutaq ki, bizdə User sinfi var və onda gizli parol saxlanılır. Parolun serializasiya olunmasını istəmirik.
import java.io.*;
public class User implements Serializable {
private String username;
private transient String password; // transient — serializasiya olunmur
public User(String username, String password) {
this.username = username;
this.password = password;
}
// User əvəzinə yalnız UserProxy-ni serializasiya edirik
private Object writeReplace() throws ObjectStreamException {
return new UserProxy(username);
}
// Proksi-sinif — yalnız serializasiya üçün
private static class UserProxy implements Serializable {
private String username;
public UserProxy(String username) {
this.username = username;
}
private Object readResolve() throws ObjectStreamException {
// Real həyatda parolu bərpa etmək mümkün deyil — parolsuz User qaytarırıq
return new User(username, "");
}
}
}
İzah:
- Serializasiya zamanı User UserProxy-yə çevrilir (parolsuz).
- Deserializasiya zamanı UserProxy yenidən User-ə çevrilir (parol artıq boşdur).
3. Dəyişməz obyektlər üçün serializasiyanın fərdiləşdirilməsi
Immutable (dəyişməz) obyektlər tez-tez özəl final sahələrdən istifadə edir və setter-ləri olmur. Standart Java serializasiyası bu məhdudiyyəti aşsa da, bəzən prosesi writeReplace/readResolve vasitəsilə açıq şəkildə idarə etmək daha məqsədəuyğundur.
Nümunə: Value Object
import java.io.*;
public final class Money implements Serializable {
private final int amount;
private final String currency;
public Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
private Object writeReplace() throws ObjectStreamException {
return new MoneyProxy(amount, currency);
}
private static class MoneyProxy implements Serializable {
private final int amount;
private final String currency;
MoneyProxy(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
private Object readResolve() throws ObjectStreamException {
return new Money(amount, currency);
}
}
}
İzah:
- Serializasiya zamanı Money MoneyProxy-yə (POJO) çevrilir.
- Deserializasiya zamanı MoneyProxy yenidən Money-ə çevrilir.
writeObject/readObject ilə qarşılıqlı əlaqə
writeReplace/readResolve metodları writeObject/readObject-dan asılı olmayaraq işləyir. Hər iki mexanizm müəyyən edilibsə, əvvəlcə writeReplace çağırılır, sonra isə qaytarılan obyekt üçün (əgər o, Serializable reallaşdırırsa) writeObject.
Sxem:
flowchart LR
A[Obyekt] -- writeReplace --> B[Proksi obyekt]
B -- writeObject --> C[Bayt axını]
C -- readObject --> D[Proksi obyekt]
D -- readResolve --> E[Yekun obyekt]
4. Təcrübə: obyektin əvəzlənməsi ilə serializasiya
Gəlin tədris tətbiqinizə xüsusi serializasiya əlavə edək — məsələn, Person sinfi üçün elə edək ki, serializasiya zamanı yalnız ad yazılsın, yaş isə nəzərə alınmasın (güman edək ki, məxfilik üçün belə edirik).
Addım 1. Əsas sinif
import java.io.*;
public class Person implements Serializable {
private String name;
private int age; // serializasiya etmək istəmirik
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Object writeReplace() throws ObjectStreamException {
return new PersonProxy(name);
}
private static class PersonProxy implements Serializable {
private final String name;
PersonProxy(String name) {
this.name = name;
}
private Object readResolve() throws ObjectStreamException {
return new Person(name, -1); // -1 — "yaş naməlumdur"
}
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
Addım 2. Test edirik
public class TestCustomSerialization {
public static void main(String[] args) throws Exception {
Person original = new Person("Alice", 30);
// Serializasiya
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.bin"));
out.writeObject(original);
out.close();
// Deserializasiya
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.bin"));
Person deserialized = (Person) in.readObject();
in.close();
System.out.println("Serializasiyadan əvvəl: " + original);
System.out.println("Deserializasiyadan sonra: " + deserialized);
}
}
Nəticə:
Serializasiyadan əvvəl: Person{name='Alice', age=30}
Deserializasiyadan sonra: Person{name='Alice', age=-1}
Gördüyünüz kimi, yaş serializasiya olunmadı — hər şey plan üzrədir!
5. Xüsusiyyətlər və nüanslar
writeReplace/readResolve nə vaxt istifadə olunmalıdır?
- Obyektin vəziyyətinin yalnız bir hissəsini serializasiya etmək lazım olduqda.
- Proksi obyektlərin serializasiyası/deserializasiyası üçün.
- Singleton nümunəsini dəstəkləmək üçün.
- Daxili quruluşu dəyişə bilən immutable və ya mürəkkəb obyektlər üçün.
Nə vaxt istifadə etməmək lazımdır?
- Əgər transient sahələr və ya writeObject/readObject ilə kifayətlənmək mümkündürsə.
- Əgər obyekt başqa obyektlə əvəzlənməməlidirsə.
Mirasalma ilə uyğunluq
Əgər super sinif writeReplace/readResolve müəyyən edibsə, onlar sub-siniflər üçün də çağırılacaq (əgər override edilməyibsə). İerarxiyalarla ehtiyatlı olun!
6. Xüsusi serializasiya zamanı tipik səhvlər
Səhv № 1: Metodların düzgün görünürlüğü deyil. Əgər writeReplace/readResolve private olmasa, serializasiya onları çağırmayacaq. Yalnız private!
Səhv № 2: Qaytarılan tiplərin uyğun gəlməməsi. writeReplace/readResolve Object qaytarmalıdır. Hətta faktiki olaraq öz tipinizi qaytarsanız belə — metodu Object qaytaran kimi elan edin.
Səhv № 3: Məlumat itkisi. Əgər proksi-obyekt ilkin obyektin bərpası üçün lazım olan bütün məlumatları daxil etmirsə, informasiyanın bir hissəsi itiriləcək. Həmişə obyektin geri bərpa oluna biləcəyini yoxlayın.
Səhv № 4: İnvariantların pozulması. readResolve proqramın gözləntilərinə cavab verən obyekti qaytarmalıdır (məsələn, singleton üçün — məhz INSTANCE).
Səhv № 5: Emal olunmamış istisnalar. writeReplace/readResolve ObjectStreamException ata bilər. Onu emal edin və ya açıq şəkildə ötürün.
GO TO FULL VERSION