1. “Xam” kolleksiyaların problemi (raw types)
Qısaca tarixçəyə nəzər salaq. Java 5-ə qədər bütün kolleksiyalar “hərşeyyeyən” idi. Onlar Object tipli obyektləri saxlayırdı və kompilyator ora nə qoyduğunuzu nəzarətdə saxlamırdı. Sətir qoymaq istəyirsiniz? Buyurun. Rəqəm? Niyə də yox. Pişik? O da olar.
// "xam" kolleksiyalar (raw types) nümunəsi, Java 5-dən əvvəl
List list = new ArrayList();
list.add("Salam");
list.add(42);
list.add(new Object());
Problem isə dəyəri “çıxarıb” istifadə edəndə üzə çıxırdı:
String s = (String) list.get(0); // OK, bu String-dir
String s2 = (String) list.get(1); // BAM! ClassCastException
Kompilyator susur, amma run-time-da siz ClassCastException alırsınız. Bu elə bil üzərində “alma” yazılmış qutudur, içində isə fincan, banan və kirpi var.
Bu niyə pisdir?
- Xətalar yalnız icra zamanı üzə çıxır.
- Tiplər qarışır: obyektləri tələb olunan tipə əl ilə çevirmək (cast) lazım olur.
- Kod daha az oxunaqlıdır və daha risklidir.
Həll — generics (ümumiləşdirmələr)
Generics (ümumiləşdirmələr) — tiplə parametrizə olunan siniflər, interfeyslər və metodlar yaratmağa imkan verən mexanizmdir. Yəni kolleksiyaya deyirsiniz: “Yalnız String-ləri saxla”, kompilyator da buna ciddi nəzarət edir.
List<String> words = new ArrayList<>();
words.add("Salam");
words.add("Dünya");
// words.add(42); // Kompilyasiya xətası! List<String>-ə int əlavə etmək olmaz
İndi kompilyator siyahıya sətirdən başqa heç nə qoymağa imkan vermir. Xəta proqram işə düşməmişdən əvvəl tutulur.
Generics-in əsas ideyası:
Kolleksiyalar (və təkcə onlar deyil) üçün tip təhlükəsizliyini təmin etmək ki, xətalar run-time-da yox, kompilyasiya mərhələsində tutulsun.
2. Generics sintaksisi: kodda necə görünür
Tipin bucaqlı mötərizələrdə göstərilməsi
Kolleksiya yaradanda, elementlərin tipini <> içində göstərin:
List<String> names = new ArrayList<>();
names.add("Alisa");
names.add("Bob");
// names.add(123); // Xəta: String-lər siyahısına rəqəm əlavə etmək olmaz
String first = names.get(0); // Tip çevirməyə ehtiyac yoxdur!
Klassika:
- List<String> — String-lər siyahısı
- List<Integer> — Integer-lər siyahısı
- Set<Double> — Double-lər çoxluğu
- Map<String, Integer> — açar String, dəyər Integer
Niyə sadəcə List yazmaq olmaz?
Mümkündür, amma generics-in bütün üstünlüklərini itirirsiniz və kompilyator xəbərdarlıq edəcək:
List list = new ArrayList(); // raw type — tövsiyə edilmir!
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // Salam, ClassCastException!
Müasir Java kodu həmişə generics-dən istifadə edir.
Diamond operatoru <>
Java 7-dən etibarən, əgər tip kontekstdən aydındırsa, sağ tərəfdə onu yazmaya bilərsiniz:
List<String> list = new ArrayList<>(); // Kompilyator özü anlayacaq ki, burada <String> var
3. Müxtəlif kolleksiyalar üçün generics
List, Set, Map üçün nümunələr
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alisa");
uniqueNames.add("Bob");
Map<String, Integer> ages = new HashMap<>();
ages.put("Alisa", 23);
ages.put("Bob", 31);
Öz sinfinizlə nümunə
class Student {
String name;
int age;
// ...
}
List<Student> students = new ArrayList<>();
students.add(new Student());
4. Faydalı nüanslar
Generics-in üstünlükləri
Tip təhlükəsizliyi. Kompilyator kolleksiyaya yalnız lazım olan tipdə elementlərin düşməsinə nəzarət edir.
Tip çevirməyə ehtiyac yoxdur. Əvvəl: String s = (String) list.get(0);. İndi: String s = list.get(0);.
Kod daha oxunaqlı və etibarlıdır. Run-time-da daha az sürpriz.
Generics məhdudiyyətləri
Primitiv tiplərdən istifadə etmək olmaz. Ümumiləşdirmələr yalnız obyektlərlə işləyir, primitivlərlə yox (int, double, boolean). Qablaşdırıcı siniflərdən istifadə edin: Integer, Double, Boolean.
List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int avtomatik olaraq Integer-ə çevrilir (autoboxing)
Tip silinməsi (type erasure) haqqında qısa məlumat
Java-da generics tip silinməsi mexanizmi ilə reallaşdırılıb: kompilyasiyadan sonra tip parametrləri barədə məlumat silinir və run-time-da JVM bunun List<String> yox, sadəcə List olduğunu bilmir. Bu, geriyə uyğunluq naminə edilib.
Nəticə: tip arqumentini konkret dəyərlə instanceof vasitəsilə yoxlamaq olmaz.
List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // Kompilyasiya xətası!
Başqa tipdən element əlavə etməyə cəhd — kompilyasiya xətası
List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // Kompilyasiya xətası: incompatible types: int cannot be converted to String
Map<String, Integer> map = new HashMap<>();
map.put("Pişik", 5);
// map.put(3, "Fil"); // Xəta: açar String olmalıdır, dəyər — Integer
Və bu əladır: xətalar kompilyasiya mərhələsində tutulur.
Yalnız kolleksiyalar deyil
Generics-i öz siniflərinizdə və metodlarınızda da istifadə edə bilərsiniz. Məsələn, universall “Qutu”:
class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> stringBox = new Box<>();
stringBox.set("Salam");
System.out.println(stringBox.get());
Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());
Kolleksiyalarda generics — standartdır, amma onları başqa yerlərdə də görəcəksiniz, məsələn, Stream API və Optional-da.
5. Generics ilə işləyərkən tipik səhvlər
Səhv №1: “xam” kolleksiyalardan istifadə. List list = new ArrayList(); kimi yazılış tip təhlükəsizliyini aradan qaldırır. Həmişə tip parametrlərini göstərin, məsələn, List<String>.
Səhv №2: Primitivlərdən istifadə etməyə cəhd. List<int> yazmaq olmaz, List<Integer> istifadə edin.
Səhv №3: Kolleksiyadan oxuyarkən əl ilə tip çevirmə. Əgər generics istifadə edirsinizsə, (String) list.get(i) kimi çevirməyə ehtiyac yoxdur. Əgər lazımdırsa — haradasa tipləri pozmusunuz.
Səhv №4: Tip parametrlərinin run-time-da əlçatan olacağını gözləmək. Tip silinməsi səbəbindən onları instanceof ilə List<String> kimi yoxlamaq olmaz.
Səhv №5: Bir kolleksiyada fərqli tipləri qarışdırmaq. Əgər List<String> elan edilibsə, Integer əlavə etməyə çalışmayın — kompilyator buraxmayacaq və bu, yaxşıdır.
GO TO FULL VERSION