إنشاء التدفقات (Streams)
إنشاء التدفقات (Streams)
قبل أن تتمكن من معالجة البيانات باستخدام Streams API تحتاج أولًا إلى تدفق. تتيح لك Java عدة طرق مصنعية ومصادر بيانات مختلفة، كل منها مناسب لموقف معين. معرفة أي نقطة بداية تختار — ولماذا — تجعل كودك أنظف بكثير من البداية بدلًا من الإكثار من الحلقات اليدوية.
لماذا يهم المصدر؟
Stream<T> هو خط أنابيب كسول (lazy) للاستخدام مرة واحدة. إنه لا يحتفظ بالبيانات؛ بل يصف كيفية سحب البيانات من مصدر وتحويلها. المصدر هو الذي يحدد نوع العناصر وضمان الترتيب، وأحيانًا ما إذا كان بإمكان التدفق العمل بكفاءة بالتوازي. لذلك اختيار المصنع المناسب ليس مجرد تفضيل شكلي — بل يؤثر على صحة الكود وأدائه.
التدفقات من المجموعات (Collections)
المصدر الأكثر شيوعًا هو أي Collection (قائمة List أو مجموعة Set أو طابور Queue). كل مجموعة ترث stream() وparallelStream() من java.util.Collection:
List.stream() يحافظ على ترتيب الإدخال. Set.stream() لا يضمن ذلك — إذ لا يوجد ترتيب محدد لـ HashSet. إذا كان الترتيب مهمًا فضّل استخدام List أو LinkedHashSet.
التدفقات من المصفوفات (Arrays)
عندما تكون بياناتك في مصفوفة استخدم Arrays.stream(). وتقبل هذه الطريقة أيضًا نطاقًا (range) لتدفق جزء من المصفوفة دون نسخها:
تُعيد Arrays.stream(int[]) تدفقًا من نوع IntStream لا Stream<Integer>. هذا مقصود — فتدفقات الأنواع الأصلية (primitive streams) تتجنب تكلفة التغليف (boxing)، وسيُغطّى هذا بالتفصيل في الدرس الثامن.
Stream.of — إنشاء تدفق من قيم مباشرة
تبني Stream.of() تدفقًا مباشرة من عدد من القيم دون الحاجة إلى إنشاء مجموعة أولًا. وهي مثالية للاختبارات والنماذج الأولية والحالات التي تملك فيها مراجع فردية:
Stream.empty() على Stream.of() بدون وسيطات عندما تريد تدفقًا فارغًا. الطريقة المخصصة تجعل القصد واضحًا وتتجنب تحذير generic غير محدد النوع.
Stream.generate — تدفقات لانهائية بناءً على Supplier
تُنشئ Stream.generate(Supplier<T>) تدفقًا لانهائيًا باستدعاء Supplier بشكل متكرر. ولأنه لا ينتهي من تلقاء نفسه يجب إضافة عملية قصيرة الدائرة (short-circuit) مثل limit():
لا تحتفظ الدالة المُمررة بذاكرة عن الاستدعاءات السابقة — كل استدعاء مستقل عن سابقه. إذا احتجت إلى عناصر تعتمد على موضعها أو على القيمة السابقة، فاستخدم Stream.iterate بدلًا من ذلك.
Stream.iterate — تدفقات لانهائية بناءً على تسلسل
تمتلك Stream.iterate صيغتين:
- الصيغة بوسيطتين (Java 8+):
Stream.iterate(seed, UnaryOperator)— لانهائية، توقف بـlimit()أوtakeWhile(). - الصيغة بثلاث وسيطات (Java 9+):
Stream.iterate(seed, Predicate, UnaryOperator)— شرط توقف مدمج، تعمل كحلقةfor.
collect() أو count() على تدفق generate غير محدود أو iterate بوسيطتين سيعمل إلى الأبد (أو حتى نفاد ذاكرة JVM). اقرن دائمًا هذه التدفقات بـ limit(n) أو takeWhile(predicate).
اختيار المصنع المناسب
- لديك
ListأوSet؟ استخدم.stream(). - لديك مصفوفة موجودة؟ استخدم
Arrays.stream(). - لديك مجموعة صغيرة من القيم المعروفة؟ استخدم
Stream.of(). - تحتاج إلى قيم مولّدة لا علاقة لكل منها بما سبقها؟ استخدم
Stream.generate(). - تحتاج إلى تسلسل حيث تعتمد كل قيمة على السابقة؟ استخدم
Stream.iterate().
الخلاصة
تتيح Java خمس طرق رئيسية لإنشاء تدفق: من Collection، أو من مصفوفة عبر Arrays.stream()، أو من قيم حرفية عبر Stream.of()، أو من مصادر لانهائية عبر Stream.generate() وStream.iterate(). لكل نوع مصدر حالة استخدام واضحة. في الدرس التالي ستُضيف مراحل التحويل الأولى — filter وmap وforEach — إلى التدفقات التي أنشأتها هنا.