1. Səhv: stream-dən təkrar istifadə
Java-da Stream — birdəfəlik alətdir. Terminal əməliyyatı icra edən kimi (məsələn, collect(), forEach(), count()) stream bağlanır və daha istifadə üçün yararsız olur.
Səhv nümunəsi
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // Hər şey qaydasındadır
stream.forEach(System.out::println); // IllegalStateException atacaq!
Çıxış:
a
b
c
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
Necə qarşısını almaq olar
Bir neçə əməliyyat etmək lazımdırsa — hər dəfə yeni stream yaradın:
List<String> list = List.of("a", "b", "c");
list.stream().forEach(System.out::println); // OK
list.stream().count(); // OK
Lifehack: Stream yeməkxananın birdəfəlik stəkanı kimidir: istifadə et — at, növbəti içki üçün yenisini götür.
2. Səhv: stream zamanı kolleksiyanı dəyişmək
Stream vasitəsilə keçid zamanı ilkin kolleksiyanı modifikasiya etsəniz, ConcurrentModificationException alacaqsınız. Bu, xüsusilə filtrasiya və elementlərin silinməsi zamanı tez-tez rast gəlinir.
Səhv nümunəsi
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
names.stream().forEach(name -> {
if (name.startsWith("A")) {
names.remove(name); // BOOM!
}
});
Nəticə:
Exception in thread "main" java.util.ConcurrentModificationException
Necə düzgün
Keçid zamanı kolleksiyanı dəyişməyən metodlardan istifadə edin və ya nəticəni yeni kolleksiyada toplayın:
names.removeIf(name -> name.startsWith("A")); // OK, removeIf təhlükəsizdir
// Yaxud Stream API ilə:
List<String> filtered = names.stream()
.filter(name -> !name.startsWith("A"))
.collect(Collectors.toList());
Yadda saxlayın: Stream-lər təmizlik sevir — stream-lər onu “yeyərkən” ilkin kolleksiyaya toxunmayın.
3. Səhv: sonsuz stream-lərdə unudulmuş limit
Stream.iterate və Stream.generate metodları sonsuz stream yarada bilər. Onları limit metodu ilə məhdudlaşdırmağı unutsanız, proqram sonsuzadək asıla bilər (və ya OutOfMemoryError-a qədər).
Səhv nümunəsi
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
infinite.forEach(System.out::println); // Proqram heç vaxt başa çatmayacaq!
Necə düzgün
Stream-i limit metodu ilə məhdudlaşdırın:
Stream<Integer> finite = Stream.iterate(0, n -> n + 1).limit(10);
finite.forEach(System.out::println); // 0-dan 9-a qədər ədədləri çap edəcək
Məsləhət: Sonsuz stream-lər final seriyası olmayan serialı xatırladır: dayanmağı kimsə etməlidir və bunu ən yaxşısı siz edin!
4. Səhv: null ilə səhv işləmə
Stream API null-u “sevmir”. Kolleksiyada null olarsa, bir çox əməliyyatlar xüsusilə elementlər üzərində metod çağırarkən NullPointerException ata bilər.
Səhv nümunəsi
List<String> names = Arrays.asList("Alice", null, "Bob");
names.stream()
.map(String::toUpperCase) // BOOM! null-da
.forEach(System.out::println);
Nəticə:
Exception in thread "main" java.lang.NullPointerException
Necə düzgün
Null dəyərləri əvvəlcədən süzün:
names.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.forEach(System.out::println);
Optional ilə iş
Əgər sizdə Stream<Optional<T>> varsa, “açmaq” üçün flatMap(Optional::stream) (Java 9+) istifadə edin:
List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty(), Optional.of("B"));
optionals.stream()
.flatMap(Optional::stream)
.forEach(System.out::println); // Yalnız "A" və "B"
Məsləhət: Kolleksiyalara null əlavə etməməyə çalışın. Əgər onsuz olmursa, onları başlanğıcda süzün.
5. Səhv: elementlərin sırasının itirilməsi
Bəzi əməliyyatlar (məsələn, flatMap, concat) elementlərin sırasına təsir göstərə bilər, xüsusilə paralel stream-lərlə işləyərkən və ya müxtəlif kolleksiyaları birləşdirərkən.
Səhv nümunəsi
List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = List.of(4, 5, 6);
Stream<Integer> combined = Stream.concat(list1.stream(), list2.stream());
combined.forEach(System.out::print); // 123456 — sıra qorunur
// Amma paralel stream-lərdən istifadə etsəniz:
Stream<Integer> par = Stream.concat(list1.parallelStream(), list2.parallelStream());
par.forEach(System.out::print); // Sıra gözlənilməz ola bilər!
Necə düzgün
Əgər sıra vacibdirsə — paralel stream-lərdən qaçın və ya sıralamaya zəmanət verən metodlardan istifadə edin (forEachOrdered):
par.forEachOrdered(System.out::print);
Fakt: Stream API susmaya görə sıranı qoruyur, yalnız siz onu “pozmasanız”.
6. Səhv: kolleksiyaların qeyri-səmərəli birləşdirilməsi
Çoxluq əməliyyatları üçün List əvəzinə Set istifadə etmək daha yaxşıdır ki, dublikatlardan qaçasınız və məhsuldarlığı artırasınız. Həmçinin flatMap-dən istifadə etmək mümkün olan yerdə iç-içə dövrlər yazmaq qeyri-səmərəlidir.
Səhv nümunəsi
List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("b", "c", "d");
// Qeyri-səmərəli:
List<String> union = new ArrayList<>(list1);
for (String s : list2) {
if (!union.contains(s)) { // List-də contains — O(n)
union.add(s);
}
}
// Set və Stream API ilə səmərəli:
Set<String> unionSet = Stream.concat(list1.stream(), list2.stream())
.collect(Collectors.toSet());
Yaxud fərq üçün:
// Qeyri-səmərəli:
List<String> diff = new ArrayList<>(list1);
diff.removeAll(list2); // OK, amma bunu stream və list.contains ilə etsəniz — yavaş olardı
// Səmərəli:
Set<String> set2 = new HashSet<>(list2);
List<String> difference = list1.stream()
.filter(s -> !set2.contains(s))
.collect(Collectors.toList());
Məsləhət: Unikal elementlərlə işləmək üçün həmişə Set istifadə edin. Böyük kolleksiyalar üçün — iç-içə dövrlərdən qaçın, stream + flatMap-ə üstünlük verin.
7. Səhv: terminal əməliyyatlarından təkrar istifadə
Bəzən stream-i iki qola “bölmək” istəyirsiniz: məsələn, elementləri siyahıda toplamaq və paralel olaraq onların sayını hesablamaq. Amma stream “birdəfəlikdir” və ikinci əməliyyat səhvlə nəticələnəcək.
Səhv nümunəsi
Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
long count = stream.count(); // BOOM! IllegalStateException
Necə düzgün
Stream-i bir neçə dəfə istifadə etmək lazımdırsa — əvvəlcə onu kolleksiyada toplayın:
List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());
long count = list.size();
Məsləhət: Hər iki nəticə lazımdırsa — əvvəlcə kolleksiyada toplayın.
8. Səhv: resursları close() etməyi unutmaq
Resurslarla bağlı stream-lərdən istifadə edirsinizsə (məsələn, Files.lines(Path)), istifadədən sonra onları bağlamağı unutmayın ki, sızmalar olmasın.
Səhv nümunəsi
Stream<String> lines = Files.lines(Path.of("file.txt"));
lines.forEach(System.out::println); // OK, amma resurs bağlanmayıb!
Necə düzgün
Try-with-resources istifadə edin:
try (Stream<String> lines = Files.lines(Path.of("file.txt"))) {
lines.forEach(System.out::println);
}
Məsləhət: Fayl stream-ləri çaydan kimidir: qaynat — söndür!
9. Səhv: flatMap-də Optional və null-un səhv işlənməsi
List<Optional<T>> tipli kolleksiyalarla işləyərkən bir çoxları map(Optional::get) istifadə edir, bu isə Optional boş olduqda istisnalara gətirib çıxarır.
Səhv nümunəsi
List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty());
optionals.stream()
.map(Optional::get) // BOOM! NoSuchElementException
.forEach(System.out::println);
Necə düzgün
flatMap(Optional::stream) (Java 9+) istifadə edin:
optionals.stream()
.flatMap(Optional::stream)
.forEach(System.out::println); // Yalnız "A"
Məsləhət: Optional sadəcə “qablaşdırma” deyil, dəyərin olmaması ilə təhlükəsiz işləmək üçün alətdir.
10. Səhv: equals/hashCode-un səhv reallaşdırılması
Çoxluqlar üzərində əməliyyatlar (Set, distinct, birləşdirmə, kəsişmə) equals və hashCode metodlarının düzgün reallaşdırılmasından asılıdır.
Səhv nümunəsi
class Student {
String name;
int age;
// equals və hashCode yoxdur!
}
Set<Student> students = new HashSet<>();
students.add(new Student("Alice", 20));
students.add(new Student("Alice", 20));
System.out.println(students.size()); // 2, halbuki onlar eyni olmalıydı!
Necə düzgün
Əgər siniflərinizi Set, Map kolleksiyalarında və ya distinct əməliyyatları üçün istifadə etməyi planlaşdırırsınızsa, equals və hashCode metodlarını mütləq override edin.
11. Səhv: collect-dən sonra stream-dən istifadə
Terminal əməliyyatından (məsələn, collect) sonra stream bağlanır. Eyni stream ilə işi davam etdirmək cəhdi səhvlə nəticələnəcək.
Səhv nümunəsi
Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
stream.filter(s -> s.startsWith("a")).forEach(System.out::println); // Exception!
Necə düzgün
Kolleksiyadan yeni stream yaradın:
list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);
12. Səhv: parallelStream-in düzgün istifadə olunmaması
Bir çoxları düşünür ki, parallelStream() həmişə sürətləndirir. Əslində, kiçik kolleksiyalar və ya sadə əməliyyatlar üçün paralellik thread idarəetmə xərclərinə görə icranı ləngidə bilər.
Səhv nümunəsi
List<Integer> numbers = IntStream.range(0, 10).boxed().collect(Collectors.toList());
numbers.parallelStream().forEach(System.out::println); // Adi stream()‑dən daha yavaş ola bilər
Necə düzgün
parallelStream-dən yalnız həqiqətən böyük kolleksiyalar və ya ağır hesablamalar üçün istifadə edin, xüsusən əməliyyatlar saf (yan təsirsiz) və assosiativ olduqda.
13. Səhv: unudulmuş terminal operatoru
Aralıq əməliyyatlar (map, filter, flatMap və s.) terminal operatoru (collect, forEach, count və s.) çağrılmayana qədər icra olunmur.
Səhv nümunəsi
Stream<String> stream = Stream.of("a", "b", "c").map(String::toUpperCase);
// Heç nə baş verməyəcək!
Necə düzgün
Mütləq terminal operatorunu çağırın:
stream.forEach(System.out::println);
GO TO FULL VERSION