1. Generics-in invariantlığı: List<Number> ≠ List<Integer>
Java-da generics sərtdir: onlar invariantdır. Bu o deməkdir ki, hətta bir tip digərinin alt tipi olsa belə (məsələn, Integer — Number-ın alt tipidir), bu tiplərlə olan kolleksiyalar bir-biri ilə əlaqəli deyil.
Təsəvvür edin: sizdə almalar üçün qutu var (List<Integer>), kimsə isə deyir ki, bu ümumiyyətlə “meyvə qutusu”dur (List<Number>). Məntiqə uyğundur, axı alma meyvədir. Amma o zaman bu qutuya banan (Double) da qoymaq mümkün olacaq və hər şey pozulacaq — axı qutu əslində “alma” üçündür.
Kod nümunəsi:
List<Integer> intList = new ArrayList<>();
List<Number> numList = intList; // Kompilyasiya xətası!
Kompilyator burada qəsdən sərtdir: o, “alma” siyahısını “meyvələr” siyahısına çevirməyə icazə vermir. Əks halda istənilən şeyi ora əlavə etmək mümkün olardı və proqram icra zamanı yıxılardı.
Ona görə də List<Number> və List<Integer> — iki tamamilə fərqli tipdir, baxmayaraq ki, Integer özü Number-ın alt tipidir.
Massivlərdən fərqli olaraq:
Integer[] intArr = new Integer[10];
Number[] numArr = intArr; // İcazəlidir (massivlər kovariantdır)
numArr[0] = 3.14; // İcra zamanı xəta (ArrayStoreException)
Nəticə: generics invariantdır və bu, tiplərin təhlükəsizliyini kompilyasiya mərhələsində təmin etmək üçündür.
2. Tip parametrlərinin sərhədləri
Bəzən generic sinifin və ya metodun hansı tiplərlə işləyə biləcəyini məhdudlaşdırmaq lazımdır. Bunun üçün sərhədlər (bounds) istifadə olunur.
Yuxarı sərhəd (extends)
class Stats<T extends Number> {
private T[] nums;
// ...
}
İndi Stats yalnız Number-dən miras alan tiplərlə (Integer, Double, Float və s.) işləyə bilər. Stats<String> yaratmağa cəhd etmək kompilyasiya xətası verəcək.
İnterfeys ilə məhdudiyyət
class Sorter<T extends Comparable<T>> {
void sort(List<T> list) { /* ... */ }
}
İndi Sorter yalnız Comparable interfeysini reallaşdıran tiplərlə işləyə bilər.
Çoxsaylı sərhədlər
Bir neçə sərhədi eyni anda & vasitəsilə göstərmək olar:
class MyClass<T extends Number & Comparable<T>> { /* ... */ }
T həm Number-dən miras almalı, həm də Comparable<T>-i reallaşdırmalıdır.
Sıra vacibdir: əvvəl sinif, sonra interfeyslər.
3. Wildcard ilə tanışlıq: ?
Wildcard — “əvəzedici” tipdir və belə deyir: “Burada hansısa tip ola bilər, amma hansının olduğunu bilmirəm”.
Nümunələr:
- List<?> — nə olursa-olsun siyahısı.
- List<? extends Number> — Number-dən miras alan istənilən tipin siyahısı (məsələn, Integer, Double).
- List<? super Integer> — Integer-in istənilən üst tipinin siyahısı (məsələn, Integer, Number, Object).
Wildcards nə üçün lazımdır?
Onlar fərqli, lakin əlaqəli tiplərin kolleksiyaları ilə işləyən metodlar yazmağa imkan verir.
Nümunə: yalnız oxuma (producer)
void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
// list.add(123); // Xəta! Elementləri əlavə etmək olmaz
}
- Elementləri Number kimi oxumaq olar.
- Əlavə etmək olmaz (yalnız null istisnadır).
Nümunə: yalnız yazma (consumer)
void addIntegers(List<? super Integer> list) {
list.add(42); // OK
// Integer x = list.get(0); // Xəta! Hansı tipin qaytarılacağını bilmirik
}
- Integer tipli (və ya onun alt tipləri) elementləri əlavə etmək olar.
- Təhlükəsiz şəkildə oxumaq olmaz Integer kimi (yalnız Object kimi).
PECS qaydası
PECS — “Producer Extends, Consumer Super”:
- Producer — Extends: kolleksiya yalnız məlumat verirsə (producer), ? extends T istifadə edin.
- Consumer — Super: kolleksiya yalnız məlumat qəbul edirsə (consumer), ? super T istifadə edin.
Yadda saxlamaq asandır: extends — oxumaq üçün (producer), super — yazmaq üçün (consumer).
Müqayisə: massivlər kovariantdır, generics invariantdır
- Massivlər kovariantdır: Integer[]-i Number[] tipli dəyişənə mənimsətmək olar.
- Generics invariantdır: List<Integer>-i List<Number> tipli dəyişənə mənimsətmək olmaz.
Wildcards generics-in invariantlığını qismən “yumşaltmağa” imkan verir.
5. Generic-metodlar və tipin çıxarılması; məhdudiyyətlər (type erasure)
Generic-metodlar
İstənilən tiplərlə işləyən generic-metodlar elan etmək olar:
public static <T> void printList(List<T> list) {
for (T elem : list) {
System.out.println(elem);
}
}
Tipin çıxarılması (type inference)
Java parametrin tipini “çıxara” bilər:
List<String> strings = List.of("a", "b");
printList(strings); // T = String
Generics məhdudiyyətləri: tiplərin silinməsi
- Java-da generics tip silinməsi vasitəsilə reallaşdırılıb: parametr tipləri haqqında məlumat kompilyasiyadan sonra silinir.
- Bayt-kodda List<String> ilə List<Integer> arasında fərq yoxdur.
- Generic tiplərin massivlərini yaratmaq olmaz: new List<String>[10] — xəta.
- Tip parametrləri ilə instanceof istifadə etmək olmaz: obj instanceof List<String> — xəta.
6. Kolleksiyalar və Stream API üzrə praktika
Siyahılar arasında elementlərin kopyalanması
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T item : src) {
dest.add(item);
}
}
- src — producer (? extends T)
- dest — consumer (? super T)
İstifadə:
List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(nums, ints); // OK: Number — Integer-in üst tipidir
Stream API və wildcards
List<Integer> ints = List.of(1, 2, 3);
List<? extends Number> numbers = ints;
numbers.stream()
.map(Number::doubleValue)
.forEach(System.out::println);
Wildcard ilə filtrasiya
public static void printAll(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
7. Tez-tez edilən səhvlər
Xəta №1: Raw types (xam tiplər).
Xam tiplərdən istifadə tip yoxlamasını söndürür və icra zamanı xətalara aparır.
List list = new ArrayList(); // raw type — pisdir!
list.add("sətir");
list.add(123); // İstənilən şeyi əlavə etmək olar
String s = (String) list.get(1); // ClassCastException!
Heç vaxt raw types istifadə etməyin. Həmişə tip parametrlərini göstərin: List<String>, List<Integer>.
Xəta №2: extends ilə təhlükəsiz olmayan çevirmələr.
? extends ... olan kolleksiyaya element əlavə etməyə cəhd kompilyasiya xətasına səbəb olacaq.
List<? extends Number> nums = new ArrayList<Integer>();
nums.add(3.14); // Kompilyasiya xətası!
Xəta №3: Aşırı yükləmədə tipin “silinməsi”.
Metodları yalnız generic parametrlərinə görə aşırı yükləmək olmaz — tiplər silindikdən sonra imzalar eyni olur.
public void process(List<String> list) { /* ... */ }
public void process(List<Integer> list) { /* ... */ } // Kompilyasiya xətası!
Xəta №4: Generic tiplərin massivləri.
Tip silinməsinə görə parametrizə olunmuş tiplərin massivlərini yaratmaq olmaz.
List<String>[] arr = new List<String>[10]; // Kompilyasiya xətası!
GO TO FULL VERSION