مقدّمة إلى Streams
مقدّمة إلى Streams
واجهة Streams API التي أُضيفت في Java 8 وتطوّرت منذ ذلك الحين تُعدّ من أبرز الإضافات إلى اللغة. تُتيح لك التعبير عن منطق معالجة البيانات كخط أنابيب تصريحي — تُخبر Java بـماذا تحسب لا بـكيفية التكرار. قبل المضي قُدُمًا، من الضروري تحديد معنى الـstream بدقة لأن الكلمة تُستخدم في سياقات متعددة في عالم الحوسبة.
ما هو الـStream؟
الـStream<T> في Java هو تسلسل من العناصر يدعم العمليات التجميعية المتسلسلة والمتوازية. ثلاثة أمور تميّزه عن المصفوفة أو القائمة العادية:
- لا تخزين. الـstream لا يحتفظ بالبيانات. يقرأ من مصدر (مجموعة، مصفوفة، ملف، أو مولّد) ويمرّر العناصر عبر خط الأنابيب عند الحاجة.
- طابع وظيفي. العمليات تنتج streams جديدة أو نتيجة نهائية؛ ولا تُعدّل المصدر أبدًا.
- تقييم كسول. العمليات الوسيطة لا تُنفَّذ حتى تطلب عملية نهائية النتيجة.
java.util.stream.Stream لا علاقة له بـjava.io.InputStream أو java.io.OutputStream. كلاهما يُسمّى "stream" لكنهما يحلّان مشكلتين مختلفتين. هذا الدرس يتناول java.util.stream فقط.
Streams مقابل Collections — الفرق الجوهري
المجموعة (مثل ArrayList أو HashSet) هي بنية بيانات: تخزّن العناصر في الذاكرة، وتتيح إضافتها وحذفها، والتكرار عليها بأي ترتيب. أما الـstream فهو طريقة عرض لمعالجة تلك البيانات؛ يُستهلك مرة واحدة ثم يُهمَل.
لنتأمّل مثالًا ملموسًا: لديك قائمة بأسماء المنتجات وتريد الأسماء التي تبدأ بـ"A" محوّلة إلى أحرف كبيرة.
أسلوب المجموعات (إلزامي):
أسلوب الـStream (تصريحي):
كلاهما يُنتج النتيجة ذاتها. نسخة الـstream تُقرأ كمواصفة: صفّي الأسماء التي تبدأ بـA، ثم حوّل كلًّا منها إلى أحرف كبيرة، ثم اجمعها في قائمة. لا حلقة صريحة، ولا تعديل لمتغيّر تراكمي، ولا فهرس للإدارة.
نموذج خط الأنابيب
كل تعبير stream يتبع البنية الثلاثية ذاتها:
- المصدر — من أين تأتي العناصر.
- العمليات الوسيطة — صفر أو أكثر من التحويلات التي ترجع stream جديدة.
- العملية النهائية — تُحفّز التنفيذ وتُنتج نتيجة (أو تأثيرًا جانبيًا).
لا شيء يعمل حتى تُستدعى العملية النهائية. لو استدعيت filter(...).map(...) دون عملية نهائية، فإن Java لا تفعل شيئًا — لا تُستدعى أجسام lambdas مطلقًا. هذا الكسل مقصود: يتيح للمشغّل دمج العمليات وتجنّب إنشاء مجموعات وسيطة.
الكسل في التطبيق العملي
لرؤية الكسل بجلاء، أضف جملة طباعة داخل عملية وسيطة:
المخرجات:
تظهر عبارة "About to trigger..." قبل أي تصفية، مما يُثبت أن العملية الوسيطة تأجّلت حتى استُدعيت count().
الـStream أحادي الاستخدام
بمجرد استدعاء عملية نهائية، يُستهلك الـstream. محاولة إعادة استخدامه تُطلق IllegalStateException.
.stream() على مجموعة المصدر مجددًا — وهو أمر غير مُكلف.
خلاصة سريعة
- الـ
Stream<T>هو خط أنابيب كسول أحادي الاستخدام فوق مصدر بيانات — لا يُخزّن شيئًا. - المجموعات تمتلك البيانات؛ الـstreams تعالجها.
- كل stream له مصدر وصفر أو أكثر من العمليات الوسيطة وعملية نهائية واحدة بالضبط.
- لا شيء ينفَّذ حتى تُستدعى العملية النهائية.
في الدرس التالي ستتعرّف على الطرق المتعددة التي تُتيحها Java لإنشاء stream — من المجموعات والمصفوفات والنطاقات والملفات والمولّدات.