الاختزال والتجميع
الاختزال والتجميع
أظهرت الدروس السابقة كيفية تصفية عناصر المجرى وتحويلها. تلك العمليات تنتج مجرىً جديدًا — لكنك في نهاية المطاف تحتاج إلى إجابة ملموسة: رقم واحد، أو قائمة، أو خريطة. هذا ما تفعله العمليات الختامية. يغطي هذا الدرس أهم ثلاث منها: reduce وcount وتجميع النتائج بـ Collectors.toList() (وبديلها الحديث).
لماذا تهمّنا العمليات الختامية؟
المجرى كسول بطبعه. الخطوات الوسيطة (filter وmap وغيرها) لا تُنفَّذ حتى تُستدعى عملية ختامية. reduce وcollect هما أقوى العمليات الختامية: الأولى تطوي المجرى في قيمة واحدة، والثانية تسكب العناصر في حاوية قابلة للتعديل كـList أو Map.
count — أبسط عملية ختامية
count() ترجع long — عدد العناصر التي نجت من خط الأنابيب.
count() مكافئة مفاهيميًا لـ reduce(0L, (acc, e) -> acc + 1) — وهذا يقودنا إلى العملية الأعم.
reduce — طيّ المجرى في قيمة واحدة
reduce تطبّق مؤثّرًا ثنائيًا بشكل متكرر حتى ينضب المجرى. فكّر فيها كطيّ على قائمة: تبدأ بقيمة هوية وتدمجها مع كل عنصر واحدًا تلو الآخر.
الشكل الأكثر شيوعًا يأخذ قيمة هوية وBinaryOperator<T>:
قيمة الهوية يجب أن تكون هوية حقيقية للعملية — إضافة الصفر لا تغير النتيجة أبدًا، لذا 0 هو هوية الجمع. وللضرب تكون الهوية 1.
يمكنك كتابة التعبير اللامبدي صراحةً لتوضيح الآلية:
reduce بدون هوية — Optional
حين لا تُعطى قيمة هوية، يرجع reduce Optional<T> لأن المجرى قد يكون فارغًا ولن تكون هناك نتيجة ذات معنى ترجعها.
Optional.
تجميع النتائج بـ Collectors.toList()
reduce رائعة لحساب قيمة عددية واحدة. لكن كثيرًا ما تريد مجموعة جديدة. هذا ما تفعله collect — تجمع عناصر المجرى في حاوية قابلة للتعديل.
أكثر مُجمِّع شيوعًا هو الذي ينتج List:
منذ Java 16 يوجد بديل أكثر إيجازًا: Stream.toList(). يرجع قائمة غير قابلة للتعديل، وهو ما تريده في الغالب:
.toList() على Collectors.toList() في كود Java 16+ . الشكل المختصر أنظف ويشير إلى الثبات من الوهلة الأولى. استخدم Collectors.toList() حين تحتاج التوافق مع Java 11 أو Java 8.
التجميع في حاويات أخرى
تقدّم فئة Collectors مُجمِّعات كثيرة — ستستكشفها بعمق في الدرس السابع. في الوقت الحالي أكثر مُجمِّعَين فائدةً بعد toList() هما:
Collectors.toSet()— ينتجSetمُزيلًا المكرّرات تلقائيًا.Collectors.joining(delimiter)— يسلسل عناصر مجرىStringفي سلسلة نصية واحدة.
الجمع بين reduce وmap — مثال عملي
نمط عملي شائع هو تحويل الكائنات إلى خاصية عددية ثم الاختزال للحصول على إحصائية ملخّصة:
reduce للآثار الجانبية. التعبير اللامبدي الذي تمرّره إلى reduce يجب أن يكون عديم الحالة وغير متداخل وترابطيًا (حتى تعطي المجاري المتوازية النفس النتيجة). تعديل الحالة الخارجية داخل اللامبدا خطأ شائع يكسر المجاري المتوازية بصمت.
الخلاصة
count() أبسط طريقة لعدّ العناصر بعد التصفية. reduce تسمح لك بطيّ أي مجرى في قيمة واحدة — استخدم شكل الهوية حين يجب أن يُرجع المجرى الفارغ نتيجة محايدة، وشكل Optional حين يكون المجرى الفارغ احتمالًا حقيقيًا. collect(Collectors.toList()) — أو .toList() الحديثة — تسكب المجرى في مجموعة ملموسة. هذه العمليات الثلاث تغطّي الغالبية العظمى من احتياجات المجاري الختامية؛ الدروس التالية ستبني عليها.