CodeGym /Kurslar /JAVA 25 SELF /Generics kolleksiyalarının seriyalaşdırılması: xüsusiyyət...

Generics kolleksiyalarının seriyalaşdırılması: xüsusiyyətləri

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

1. Generics kolleksiyalarının seriyalaşdırılması və deseriyalaşdırılması

Java-da generics (ümumiləşdirmələr) sehr deyil, daha çox kompilyator tərəfindən dəstəklənən bir illüziyadır. Kompilyasiya mərhələsində generics tip parametrləri haqqında məlumat silinir (buna type erasure, yəni “tiplərin silinməsi” deyilir). Yəni icra vaxtında (runtime) List<String> kolleksiyası List<Object> və ya List<Integer>-dən fərqlənmir. Onların hamısı sadəcə List-dir və JVM orada hansı konkret tiplərin saxlandığını bilmir.

Gəlin bir nümunəyə baxaq:

List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();

System.out.println(stringList.getClass() == intList.getClass()); // true!

Burada hər şey bir az hiyləgər qurulub. Biz iki siyahı yaradırıq — biri sətirlər, digəri ədədlər üçün. Kompilyator səviyyəsində Java ciddi nəzarət edir ki, List<String>-ə sətirdən başqa heç nə, List<Integer>-ə isə ədəddən başqa heç nə qoymayasınız. Lakin proqram işə düşən kimi fərqlər yox olur. JVM üçün hər iki obyekt — sadəcə ArrayList-dir və orada hansı elementlərin saxlanmalı olduğunu yoxlamaq artıq mümkün deyil. Məhz buna görə iki siyahının siniflərini müqayisə etmək (stringList.getClass() == intList.getClass()) true qaytarır.

Buradan vacib nəticə çıxır: Java-da generics ilk növbədə kompilyasiya mərhələsində rahatlıq və təhlükəsizlik üçündür. Amma runtime-da bu “etiketlər” itir. Buna görə, kolleksiyanı seriyalaşdıranda fayla yalnız verilənlərin özləri düşür, generic tipləri barədə informasiya isə düşmür. Yəni dəyərlərin siyahısı saxlanacaq, amma fayla baxaraq bunun məhz List<String> yoxsa List<Object> və ya List<Integer> olduğunu anlamaq mümkün deyil.

Bir başqa nümunə: List<String>-in seriyalaşdırılması və deseriyalaşdırılması

import java.io.*;
import java.util.*;

public class GenericSerializationDemo {
    public static void main(String[] args) throws Exception {
        List<String> fruits = new ArrayList<>();
        fruits.add("Alma");
        fruits.add("Banan");
        fruits.add("Portağal");

        // Seriyalaşdırma
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("fruits.ser"))) {
            oos.writeObject(fruits);
        }

        // Deseriyalaşdırma
        List<String> loadedFruits;
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("fruits.ser"))) {
            loadedFruits = (List<String>) ois.readObject();
        }

        System.out.println(loadedFruits); // [Alma, Banan, Portağal]
    }
}

Bu sətrə diqqət yetirin:

loadedFruits = (List<String>) ois.readObject();

Burada nəticəni açıq şəkildə List<String> tipinə gətiririk, halbuki runtime-da bu sadəcə ArrayList-dir. Kompilyator bunun həqiqətən sətirlər siyahısı olduğunu yoxlaya bilmir və əgər orada birdən sətir olmayan nəsə olsa — proqram işləyərkən ClassCastException alacağıq.

2. Generics kolleksiyalarının deseriyalaşdırılmasında problemlər

Elementin tipi haqqında məlumatın itməsi

Generic parametrləri barədə məlumat silindiyi üçün, deseriyalaşdırmadan sonra Java kolleksiyada məhz gözlədiyiniz obyektlərin olduğunu təmin edə bilmir. Aldığınız şey — “xam” kolleksiya (raw type)-dır və kompilyator etiraz etmir, lakin problem runtime-da üzə çıxa bilər.

Problemin nümayişi

List rawList = new ArrayList();
rawList.add("Pişik");
rawList.add(42); // Integer!
// Deseriyalaşdırma
List<String> loadedCats = (List<String>) ois.readObject();
String cat = loadedCats.get(1); // ClassCastException!

Unchecked cast xəbərdarlığı

Kompilyator potensial problem barədə sizi açıq şəkildə xəbərdar edəcək:

Note: GenericSerializationDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Bu xəbərdarlıq onu bildirir ki, siz tipi yoxlamadan çevirirsiniz və kolleksiyada gözlənilməyən tipli obyektlər ola bilər.

3. Generics kolleksiyalarının seriyalaşdırılmasının xüsusiyyətləri

Faylda generic parametrləri barədə məlumat yoxdur

Siz List<String>List<Integer> seriyalaşdıranda, faylda bunun sətirlər və ya ədədlər olduğuna dair heç bir məlumat olmur. Kolleksiyanın məzmunu “olduğu kimi” seriyalaşdırılır — obyektlər ardıcıllıqla.

Seriyalaşdırılmış faylı mətn redaktorunda açsanız, orada <String> və ya <Integer> haqqında heç nə görməyəcəksiniz. Bütün bunlar yalnız mənbə kodu və kompilyator səviyyəsindədir.

Nümunə: müxtəlif kolleksiyaların seriyalaşdırılması

List<Integer> numbers = Arrays.asList(1, 2, 3);
List<String> words = Arrays.asList("bir", "iki", "üç");

// Seriyalaşdırma
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"))) {
    oos.writeObject(numbers);
    oos.writeObject(words);
}

test.ser faylında — sadəcə iki ArrayList tipli obyekt var, generic parametrləri barədə heç bir məlumat yoxdur.

“Xam” kolleksiyaların deseriyalaşdırılması problemi

Əgər siz siyahını generic parametri olmadan (raw type) seriyalaşdırmısınız və onu List<String> kimi deseriyalaşdırırsınızsa, kompilyator tiplərin düzgünlüyünü yoxlaya bilməyəcək və runtime-da xətalar mümkündür.

4. Generics kolleksiyalarını seriyalaşdırarkən ən yaxşı təcrübələr

Gözlənilən element tiplərini sənədləşdirin.
Əgər sizin API kolleksiyanı seriyalaşdırırsa, mütləq hansı tip elementlərin gözlənildiyini göstərin. Məsələn: “Bu metod seriyalaşdırılmış List<User> qaytarır”.

Deseriyalaşdırmadan sonra elementlərin tiplərini yoxlayın.
Kolleksiya deseriyalaşdırıldıqdan sonra, xüsusilə də mənbəyə nəzarət sizdə deyilsə, bütün elementlərin gözlənilən tipdə olduğunu yoxlamaq faydalıdır.

for (Object obj : loadedList) {
    if (!(obj instanceof String)) {
        throw new IllegalStateException("Sətir gözlənilirdi, lakin tapıldı: " + obj.getClass());
    }
}

Dəyişməz kolleksiyalardan istifadə edin.
Kolleksiyanı yalnız oxumaq üçün seriyalaşdırırsınızsa, dəyişməz kolleksiyalardan istifadə edin — List.copyOf, Collections.unmodifiableList. Bu, deseriyalaşdırmadan sonra təsadüfi dəyişikliklərin qarşısını almağa kömək edəcək.

Eyni kolleksiyada tipləri qarışdırmayın.
Daxilində müxtəlif siniflər olan List<Object> kimi, elementləri fərqli tiplərdən ibarət kolleksiyaları seriyalaşdırmamaq çalışın. Bu, deseriyalaşdırmanı çətinləşdirir və xətalara səbəb ola bilər.

Xəbərdarlıqların söndürülməsini ehtiyatla istifadə edin.
Əgər elementlərin doğru tipdə olduğuna əminsinizsə, kompilyator xəbərdarlığını @SuppressWarnings("unchecked") annotasiyası ilə söndürə bilərsiniz:

@SuppressWarnings("unchecked")
List<String> loaded = (List<String>) ois.readObject();

Lakin bunu şüurlu edin — problemi production-a qədər gizlətmək asandır.

5. Nümunə: öz sinfi olan kolleksiyanın seriyalaşdırılması və deseriyalaşdırılması

Tutaq ki, bizdə User sinfi var:

import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private int age;
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return name + " (" + age + ")";
    }
}

İstifadəçilər siyahısını seriyalaşdıraq:

List<User> users = Arrays.asList(
    new User("Alisa", 30),
    new User("Bob", 25)
);

// Seriyalaşdırma
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users.ser"))) {
    oos.writeObject(users);
}

// Deseriyalaşdırma
List<User> loadedUsers;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users.ser"))) {
    loadedUsers = (List<User>) ois.readObject();
}

System.out.println(loadedUsers); // [Alisa (30), Bob (25)]

Hər şey işləyir! Amma kim isə seriyalaşdırılmış fayla başqa sinifdən obyekt qoysa, elementləri User kimi oxumağa cəhd edəndə ClassCastException ala bilərsiniz.

6. İç-içə generics kolleksiyalarının seriyalaşdırılması

Kolleksiyalar iç-içə ola bilər, məsələn: List<List<String>>, Map<String, List<User>> və s. Java bu cür strukturları rekursiv şəkildə seriyalaşdırır, amma qaydalar dəyişmir:

  • Bütün daxili kolleksiyalar və elementlər seriyalaşdırıla bilən olmalıdır.
  • Generic parametrləri barədə məlumat yenə də silinir.

Nümunə: siyahılar siyahısının seriyalaşdırılması

List<List<String>> matrix = new ArrayList<>();
matrix.add(Arrays.asList("a", "b"));
matrix.add(Arrays.asList("c", "d"));

// Seriyalaşdırma
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("matrix.ser"))) {
    oos.writeObject(matrix);
}

// Deseriyalaşdırma
List<List<String>> loadedMatrix;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("matrix.ser"))) {
    loadedMatrix = (List<List<String>>) ois.readObject();
}

System.out.println(loadedMatrix); // [[a, b], [c, d]]

7. Faydalı nüanslar

Müxtəlif reallaşdırmalarla generics kolleksiyalarının seriyalaşdırılması

Bəzən siz bir kolleksiyanın bir reallaşdırmasını seriyalaşdırır, amma başqa reallaşdırma kimi deseriyalaşdırırsınız. Məsələn, ArrayList seriyalaşdırmısınız, amma LinkedList kimi deseriyalaşdırırsınız. Bu, tip çevirmə xətasına gətirib çıxaracaq:

List<String> list = new ArrayList<>();
// ...
List<String> loaded = (LinkedList<String>) ois.readObject(); // ClassCastException!

Məsləhət: Həmişə seriyalaşdırdığınız eyni tipə deseriyalaşdırın və ya konkret reallaşdırma önəmli deyilsə, interfeysdən (List) istifadə edin.

Kitabxanaların istifadəsi (məsələn, Gson, Jackson)

JSON üçün kitabxanalar (məsələn, Gson, Jackson) generics-li kolleksiyaları seriyalaşdırıb/deseriyalaşdıra bilir, lakin tiplərin silinməsi səbəbindən deseriyalaşdırma zamanı tipin açıq şəkildə göstərilməsini tələb edir. Gson üçün nümunə:

Type type = new com.google.gson.reflect.TypeToken<List<User>>(){}.getType();
List<User> users = gson.fromJson(json, type);

8. Map və Set-də generics və seriyalaşdırma

Yuxarıdakı qaydalar generics-li digər kolleksiyalar üçün də keçərlidir:

  • Map<String, Integer> seriyalaşdırılarkən açar və dəyərlərin tipləri barədə məlumat saxlanmır.
  • Deseriyalaşdırma zamanı lazım olan tipə çevirmə aparmaq və məzmuna diqqətli olmaq tələb olunur.

Nümunə: Map-in seriyalaşdırılması

Map<String, Integer> scores = new HashMap<>();
scores.put("Vasya", 90);
scores.put("Petya", 85);

// Seriyalaşdırma
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("scores.ser"))) {
    oos.writeObject(scores);
}

// Deseriyalaşdırma
Map<String, Integer> loadedScores;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("scores.ser"))) {
    loadedScores = (Map<String, Integer>) ois.readObject();
}

System.out.println(loadedScores); // {Vasya=90, Petya=85}

9. Generics kolleksiyalarını seriyalaşdırarkən tipik səhvlər

Səhv №1: Deseriyalaşdırmada ClassCastException. Əgər kolleksiyanı List<String> kimi deseriyalaşdırırsınızsa, amma içində başqa tipdən obyekt çıxırsa, runtime-da ClassCastException alacaqsınız. Kolleksiyanın məzmununu həmişə yoxlayın!

Səhv №2: Seriyalaşdırıla bilməyən elementə görə NotSerializableException. Kolleksiyadakı elementlərdən azı biri Serializable reallaşdırmırsa, seriyalaşdırma NotSerializableException xətası ilə başa çatacaq. Kolleksiyada ola biləcək bütün siniflərin seriyalaşdırıla bildiyini yoxlayın.

Səhv №3: Generic parametrləri məlumatının itməsi. Deseriyalaşdırmadan sonra generic parametrlərinə güvənməyin — onlar runtime-da yoxdur. Məlumatın düzgünlüyünə şübhəniz varsa, açıq tip yoxlamalarından istifadə edin.

Səhv №4: Kolleksiya reallaşdırmalarının uyğun gəlməməsi. ArrayList seriyalaşdırmısınız, amma LinkedList kimi deseriyalaşdırırsınız — çevirmə xətası alacaqsınız. Seriyalaşdırılan eyni tipə deseriyalaşdırmağa çalışın.

Səhv №5: Sinif versiyalarının uyğunsuzluğu. Kolleksiya elementinin sinif quruluşu seriyalaşdırmadan sonra dəyişibsə (məsələn, sahə əlavə olunub), deseriyalaşdırma zamanı xətalar mümkündür. Versiya nəzarəti üçün serialVersionUID-dən istifadə edin.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION