CodeGym /Kurslar /JAVA 25 SELF /Primitiv stream-lər və qablaşdırmanın (boxing) xərci

Primitiv stream-lər və qablaşdırmanın (boxing) xərci

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

1. Problem: nə üçün adi Stream həmişə səmərəli deyil

Java-da rəqəm kolleksiyaları ilə işləyərkən və adi stream-lərdən (Stream<Integer>, Stream<Double>) istifadə edərkən, pərdəarxasında primitiv dəyərlərin obyekt qabıqlarına (Integer, Double və s.) “qablaşdırılması” (boxing) və “açılması” (unboxing) baş verir. Bu rahatdır, amma həmişə səmərəli deyil:

  • Boxing — primitivin (int) obyektə (Integer) çevrilməsidir.
  • Unboxing — əks əməliyyat: obyektdən primitivə.

Problem:
Boxing/unboxing əlavə əməliyyatlardır və yaddaş sərf edir. Proqramın “isti” (tez‑tez çağırılan) hissələrində bu, xüsusilə böyük ədədlər massivlərini emal edərkən, performansın nəzərəçarpacaq dərəcədə enməsinə səbəb ola bilər.

Nümunə:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().map(x -> x * 2).reduce(0, Integer::sum);

Burada hər bir ədəd — bu, Integer obyektidir, primitiv int deyil.

2. Primitiv stream-lər: IntStream, LongStream, DoubleStream

Artıq boxing/unboxing-dən qaçmaq üçün Java-da primitiv stream-lər var:

  • IntStreamint üçün
  • LongStreamlong üçün
  • DoubleStreamdouble üçün

Onlar yalnız primitivlərlə işləyir və əlavə obyekt‑qabıqlar yaratmır.

Primitiv stream necə yaradılır?

Massivdən:

int[] arr = {1, 2, 3, 4, 5};
IntStream s = Arrays.stream(arr);

range/rangeClosed ilə:

IntStream.range(0, 10)          // 0..9
IntStream.rangeClosed(1, 5)     // 1..5 daxil olmaqla

Təsadüfi ədədlərin generasiyası:

new Random().ints(5, 0, 100)    // 0-dan 99-a qədər 5 təsadüfi int

Nümunə: massiv elementlərinin cəmlənməsi

int[] arr = {1, 2, 3, 4, 5};
int sum = Arrays.stream(arr).sum(); // 15

3. Stream<T> ilə primitiv stream-lər arasında çevirmə

Bəzən sizdə adi stream olur, bəzən isə primitiv. Çevirmə üçün xüsusi metodlar istifadə olunur:

  • mapToInt, mapToLong, mapToDouble — adi stream-i primitivə çevirir.
  • boxed() — primitiv stream-i obyekt stream-inə geri çevirir.

Nümunə:

List<String> words = List.of("Java", "Stream", "API");
IntStream lengths = words.stream().mapToInt(String::length);
lengths.forEach(System.out::println); // 4 6 3

Əksinə:

IntStream ints = IntStream.range(1, 5);
Stream<Integer> boxed = ints.boxed();

Diqqət:
boxed() — bu, geri çevirmə əməliyyatıdır, yenidən obyekt‑qabıqlar (Integer, Double və s.) yaradır və əgər maksimal performans lazımdırsa — onu “isti” yerlərdə istifadə etməkdən qaçın.

4. Cəmləmə və xülasələr: sum, average, min, max, summaryStatistics

Primitiv stream-lərdə aqreqasiya üçün əlverişli metodlar var:

  • sum() — bütün elementlərin cəmi
  • average() — orta (qaytarır: OptionalDouble)
  • min(), max() — minimum və maksimum (OptionalInt, OptionalLong, OptionalDouble)
  • summaryStatistics() — tam statistika obyektini qaytarır (cəmi, orta, minimum, maksimum, say)

Nümunə:

int[] arr = {1, 2, 3, 4, 5};
IntSummaryStatistics stats = Arrays.stream(arr).summaryStatistics();
System.out.println(stats.getSum());      // 15
System.out.println(stats.getAverage());  // 3.0
System.out.println(stats.getMin());      // 1
System.out.println(stats.getMax());      // 5
System.out.println(stats.getCount());    // 5

Collectors ilə müqayisə:
Adi stream-lər üçün Collectors.summarizingInt istifadə etmək olar, lakin bu, çox vaxt primitiv stream metodlarından daha az səmərəlidir:

List<Integer> nums = List.of(1, 2, 3, 4, 5);
IntSummaryStatistics stats = nums.stream().collect(Collectors.summarizingInt(x -> x));

5. Autoboxing-dən qaçmaq: harada vacibdir və necə ölçmək olar

Harada bu kritikdir?

  • Böyük həcmdə ədədləri emal edən dövrlərdə və stream-lərdə (məsələn, massivlərin emalı, statistika, riyaziyyat, verilənlərin pars edilməsi).
  • “İsti” yerlərdə — tez‑tez çağırılan və performansa təsir edən kod.

Niyə vacibdir?

  • Hər dəfə boxing baş verəndə yeni obyekt‑qabı (Integer, Double və s.) yaradılır.
  • Bu, zibil toplayıcısına (GC) yükü artırır.
  • Mikrobençmarlarda fərq bəzən dəfələrlə ola bilər!

Necə ölçmək olar?
Dəqiq müqayisə üçün JMH (Java Microbenchmark Harness) çərçivəsindən istifadə olunur. O, boxing ilə və onsuz kodun performansını düzgün müqayisə etməyə imkan verir.

Nümunə (psevdo‑kod):

@Benchmark
public int sumIntStream() {
    return IntStream.range(0, 1_000_000).sum();
}

@Benchmark
public int sumStreamInteger() {
    return Stream.iterate(0, n -> n + 1).limit(1_000_000).reduce(0, Integer::sum);
}

İkinci variant, Integer obyektlərinin daimi yaradılması səbəbindən daha yavaş işləyəcək.

Nəticə:
Əgər performans sizin üçün vacibdirsə — primitiv stream-lərdən istifadə edin!

6. Primitiv stream-lər həqiqətən nə vaxt kömək edir və nə vaxt mürəkkəbləşdirməyə dəyməz

Aşağıdakı hallarda primitiv stream-lərdən istifadə edin:

  • Böyük ədəd massivləri/kolleksiyaları ilə işləyirsinizsə.
  • Cəmi, ortanı, minimumu, maksimumu tez hesablamaq lazımdırsa.
  • Hər millisekundun önəmli olduğu kod yazırsınızsa (məsələn, real vaxtda verilənlərin emalı).

Aşağıdakı hallarda isə özünüzü yormaya bilərsiniz:

  • Kolleksiya kiçikdirsə (onlarla və ya daha az element).
  • Tip çevirmələri səbəbindən kod həddən artıq mürəkkəbləşirsə.
  • Performans kritik deyilsə (məsələn, istifadəçi daxilinin emalı).

Nümunə:

List<Integer> smallList = List.of(1, 2, 3);
int sum = smallList.stream().mapToInt(x -> x).sum(); // Olar, lakin adi reduce da uyğundur

Məsləhət: mikroskopik optimallaşdırma naminə kodu “cəngəlliyə” çevirməyin. Primitiv stream-ləri yalnız həqiqətən əsaslandırıldığı yerlərdə istifadə edin.

7. OptionalInt, OptionalDouble: nəticələrin təhlükəsiz əldə edilməsi

Primitiv stream-lərdə min(), max(), average() metodları sadəcə ədəd yox, “qabıq” — OptionalInt, OptionalDouble və s. qaytarır. Bu, boş stream-ləri təhlükəsiz idarə etmək üçün lazımdır (məsələn, massiv boşdursa).

Nümunə:

int[] arr = {};
OptionalInt min = Arrays.stream(arr).min();
if (min.isPresent()) {
    System.out.println("Minimum: " + min.getAsInt());
} else {
    System.out.println("Massiv boşdur!");
}

Adi Optional ilə müqayisə:

  • OptionalIntint üçün
  • OptionalDoubledouble üçün
  • OptionalLonglong üçün

Niyə sadəcə 0 qaytarmayaq?
Çünki 0 etibarlı bir dəyər ola bilər, boş stream isə ayrıca vəziyyətdir.

average ilə nümunə:

double[] arr = {};
OptionalDouble avg = Arrays.stream(arr).average();
double result = avg.orElse(Double.NaN); // boşdursa — NaN qaytaracaq

8. Təcrübə: primitiv stream-lərin istifadəsinə nümunələr

Nümunə 1: 1‑dən 1000‑ə qədər ədədlərin kvadratlarının cəmi

int sum = IntStream.rangeClosed(1, 1000)
                   .map(x -> x * x)
                   .sum();
System.out.println(sum);

Nümunə 2: Cüt ədədlərin filtrasiyası və sayılması

int[] arr = {1, 2, 3, 4, 5, 6};
long count = Arrays.stream(arr)
                   .filter(x -> x % 2 == 0)
                   .count();
System.out.println("Cüt ədədlərin sayı: " + count);

Nümunə 3: adi Stream<Integer> ilə müqayisə

List<Integer> list = IntStream.range(0, 1_000_000)
                              .boxed()
                              .collect(Collectors.toList());

long t1 = System.currentTimeMillis();
int sum1 = list.stream().mapToInt(x -> x).sum();
long t2 = System.currentTimeMillis();
System.out.println("Stream<Integer>: " + (t2 - t1) + " ms");

t1 = System.currentTimeMillis();
int sum2 = IntStream.range(0, 1_000_000).sum();
t2 = System.currentTimeMillis();
System.out.println("IntStream: " + (t2 - t1) + " ms");

Böyük həcmlərdə fərq nəzərə çarpacaq.

9. Primitiv stream-lərlə işləyərkən tipik səhvlər

Səhv №1: Tipləri çevrilərkən boxing-i unutmaq.
Əgər boxed() istifadə edirsinizsə, yadda saxlayın ki, bu, yenidən obyekt‑qabıqlar yaradır. Zərurət olmadan istifadə etməyin.

Səhv №2: Obyektlər üçün primitiv stream-lərdən istifadə.
IntStream yalnız int ilə işləyir, obyektlərlə yox. Obyektlərlə işləmək lazımdırsa — adi Stream<T> istifadə edin.

Səhv №3: OptionalInt/OptionalDouble-i nəzərə almamaq.
Əgər min(), max(), average() çağırırsınızsa — həmişə nəticənin mövcud olduğunu (isPresent()) yoxlayın, əks halda istisna alacaqsınız.

Səhv №4: Stream<T> ilə IntStream arasında həddən artıq mürəkkəb çevirmələr.
Əgər kod daimi mapToInt()boxed()mapToDouble() səbəbindən oxunmaz olur — bəlkə də məntiqi sadələşdirmək lazımdır.

Səhv №5: Kiçik kolleksiyalarda “möcüzəvi” sürətlənmə gözləmək.
Kiçik siyahılarda Stream ilə IntStream arasındakı fərq minimaldır. Mikroskopik qənaət naminə kodu mürəkkəbləşdirməyə dəyməz.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Mağazanın günlük gəlirinin hesablanması 💰
Mağazanın günlük gəlirinin hesablanması 💰
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Sehrli Akademiya üçün sehrlərin uzunluğunun təhlili ✨
Sehrli Akademiya üçün sehrlərin uzunluğunun təhlili ✨
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION