flatMap والتحويل
flatMap والتحويل
تعلمت سابقًا أن map() تحوّل كل عنصر في التدفق إلى شيء آخر — إدخال واحد، وإخراج واحد. لكن ماذا يحدث حين ينتج عنصر واحد عدة عناصر بطبيعته؟ هذه بالضبط المشكلة التي تحلّها flatMap(). إتقان متى تلجأ إلى flatMap بدلًا من map هو أحد أهم المهارات عند العمل مع Streams API.
المشكلة: الهياكل المتداخلة
تخيّل أن لديك قائمة من الجمل وتريد تدفقًا يحتوي على كل كلمة على حدة. باستخدام map() العادية ستحصل على Stream<String[]> — تدفق من المصفوفات — وليس تدفقًا مسطّحًا من النصوص:
كل جملة تحوّلت إلى مصفوفة، وأصبح لديك تدفق من المصفوفات. التكرار عليه يعطيك مصفوفات وليس كلمات. flatMap() تحلّ ذلك بتحويل كل عنصر إلى تدفق ثم دمج جميع تلك التدفقات الفرعية في تدفق واحد متصل.
map هي علاقة واحد-لواحد. flatMap هي واحد-لكثير — كل عنصر مصدر ينتج تدفقًا من النتائج، وتلك التدفقات كلها تُدمج في تدفق الإخراج الواحد.
flatMap في العمل
الـ lambda الممررة إلى flatMap يجب أن تُعيد Stream. هنا Arrays.stream(s.split(" ")) تُنتج تدفقًا صغيرًا لكل جملة. تقوم الـ API بدمج كل تلك التدفقات في تدفق واحد قبل أن تراه أي عملية لاحقة.
تسطيح قائمة من القوائم
حالة استخدام كلاسيكية هي المجموعات المتداخلة — مثلًا، كل قسم يحتوي على قائمة موظفين:
مع map كنت ستحصل على Stream<List<String>>. مع flatMap تحصل على Stream<String> واحد — كل موظف في كل قسم، جاهزًا لعمليات إضافية كـ sorted() أو distinct().
الجمع بين flatMap وعمليات أخرى
بما أن flatMap تُنتج تدفقًا عاديًا، يمكنك تسلسل أي عملية لاحقة بعدها. نمط شائع هو التسطيح ثم التصفية ثم الجمع:
Collection::stream كمرجع دالة بدلًا من كتابة list -> list.stream() حين يكون نوع العنصر مجموعة معروفة. فهو أكثر إيجازًا وبالقدر ذاته من الوضوح.
flatMap مقابل map: الاختيار الصحيح
- إذا كانت الدالة تُعيد قيمة واحدة محوّلة، استخدم
map. - إذا كانت الدالة تُعيد مجموعة أو تدفقًا من القيم ينبغي دمجها في التدفق الرئيسي، استخدم
flatMap. - إذا استخدمت
mapبينما كنت تحتاجflatMap، سيصبح نوع العنصرStream<Stream<T>>أوStream<List<T>>— دلالة شائعة وقت التصريف على أنك اخترت الخاطئة.
المتغيرات العددية: flatMapToInt وflatMapToLong وflatMapToDouble
تمامًا كما أن لـ map نظيرًا mapToInt لتجنّب التغليف، لـ flatMap متغيرات متخصصة في الأنواع البدائية. إذا كان تدفقك الفرعي يُنتج قيم int، استخدم flatMapToInt للبقاء بدون تغليف:
Stream<Integer> حين يكون لديك مصفوفات بدائية. سيُغلَّف كل int إلى Integer مما يضيف تخصيصات كومة غير ضرورية. فضّل flatMapToInt وابقَ على IntStream للعمليات العددية.
مثال واقعي: استخراج الوسوم
افترض منصة تدوين حيث كل منشور يحمل قائمة من الوسوم. تريد أعلى 5 وسوم الأكثر استخدامًا عبر جميع المنشورات:
الخلاصة
flatMap هي الأداة المخصصة لتسطيح علاقات الواحد-لكثير في مسار التدفق. القاعدة الأساسية: يجب أن تُعيد الدالة الممررة Stream، وتُدمج كل تلك التدفقات الفرعية تلقائيًا في تدفق واحد. كلما وجدت نفسك تُطبّق map على مجموعة ثم تعاني من تدفق مكوّن من مجموعات، الجأ إلى flatMap. للعمل العددي، تُلغي flatMapToInt وflatMapToLong وflatMapToDouble تكلفة التغليف.