لقد استخدمنا طريقة التجميع () عدة مرات من قبل لدمج العناصر التي تم إرجاعها بواسطة الدفق في قائمة ArrayList. هذه عملية تقليل، وهي مفيدة لتحويل مجموعة إلى نوع آخر (عادةً مجموعة قابلة للتغيير). يمكن أن توفر الدالة Collect()، إذا تم استخدامها مع بعض الأساليب في فئة أداة Collectors، راحة كبيرة، كما سنقدمه في هذا القسم.
دعنا نستمر في استخدام قائمة الأشخاص السابقة كمثال لنرى ما يمكن أن تفعله طريقة التجميع (). لنفترض أننا نريد العثور على جميع الأشخاص الذين تزيد أعمارهم عن 20 عامًا من القائمة الأصلية. فيما يلي الإصدار الذي تم تنفيذه باستخدام قابلية التغيير وطريقة forEach():
انسخ رمز الكود كما يلي:
List<Person> SeniorThan20 = new ArrayList<>();
.filter(person -> person.getAge() > 20)
.forEach(person -> SeniorThan20.add(person)); System.out.println("الأشخاص الأكبر من 20 عامًا: " + SeniorThan20);
نستخدم طريقة التصفية () لتصفية جميع الأشخاص الذين تزيد أعمارهم عن 20 عامًا من القائمة. بعد ذلك، في طريقة forEach، نضيف عناصر إلى قائمة ArrayList التي تمت تهيئتها مسبقًا. دعونا نلقي نظرة على مخرجات هذا الكود أولاً، ثم نعيد بنائه لاحقًا.
انسخ رمز الكود كما يلي:
الأشخاص الأكبر من 20 عامًا: [سارة - 21، جين - 21، جريج - 35]
إخراج البرنامج صحيح، ولكن لا تزال هناك مشكلة صغيرة. أولاً، تعد إضافة عناصر إلى المجموعة عملية منخفضة المستوى - فهي أمر حتمي وليست تصريحية. إذا أردنا تحويل هذا التكرار إلى متزامن، فيجب علينا أن نأخذ في الاعتبار مشكلات سلامة الخيط - فالتنوع يجعل من الصعب الموازاة. ولحسن الحظ، يمكن حل هذه المشكلة بسهولة باستخدام طريقة التجميع (). دعونا نرى كيف يتم تحقيق ذلك.
تقبل طريقة التجميع () الدفق وتجمعها في حاوية النتائج. وللقيام بذلك، لا بد من معرفة ثلاثة أشياء:
+ كيفية إنشاء حاوية نتائج (على سبيل المثال، باستخدام طريقة ArrayList::new) + كيفية إضافة عنصر واحد إلى الحاوية (على سبيل المثال، باستخدام طريقة ArrayList::add) + كيفية دمج مجموعة نتائج واحدة في مجموعة أخرى (على سبيل المثال، استخدام طريقة ArrayList: :addAll)
العنصر الأخير غير مطلوب للعمليات التسلسلية؛ فالرمز مصمم لدعم العمليات التسلسلية والمتوازية.
نحن نقدم هذه العمليات لطريقة التجميع ونسمح لها بجمع الدفق الذي تمت تصفيته.
انسخ رمز الكود كما يلي:
قائمة<شخص> أقدم من 20 =
People.stream()
.filter(person -> person.getAge() > 20)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println("الأشخاص الأكبر من 20 عامًا:" + SeniorThan20);
نتيجة هذا الكود هي نفسها السابقة، ولكن هناك العديد من المزايا لكتابته بهذه الطريقة.
بادئ ذي بدء، تعد طريقة البرمجة لدينا أكثر تركيزًا وتعبيرًا، وتنقل بوضوح الغرض من جمع النتائج في قائمة ArrayList. المعلمة الأولى لجمع () هي مصنع أو منتج، والمعلمة اللاحقة هي عملية تستخدم لجمع العناصر.
ثانيًا، نظرًا لأننا لا نجري تعديلات صريحة على الكود، فيمكننا بسهولة إجراء هذا التكرار بالتوازي. لقد سمحنا للمكتبة الأساسية بالتعامل مع التعديلات، وسوف تهتم بقضايا التنسيق وسلامة الخيط، على الرغم من أن ArrayList نفسها ليست آمنة لسلسلة الرسائل - عمل جيد.
إذا سمحت الظروف، يمكن لطريقة التجميع () إضافة عناصر إلى قوائم فرعية مختلفة بالتوازي، ثم دمجها في قائمة كبيرة بطريقة آمنة للخيط (يتم استخدام المعلمة الأخيرة لعملية الدمج).
لقد رأينا أن هناك الكثير من الفوائد لاستخدام طريقة التجميع () بدلاً من إضافة العناصر يدويًا إلى القائمة. دعونا نلقي نظرة على نسخة مثقلة من هذه الطريقة - إنها أبسط وأكثر ملاءمة - فهي تأخذ Collector كمعلمة. هذا المجمع عبارة عن واجهة تتضمن المنتجين والمجمعين والمجمعين. في الإصدارات السابقة، تم تمرير هذه العمليات إلى الأساليب كمعلمات مستقلة. يعد استخدام المجمع أبسط ويمكن إعادة استخدامه. توفر فئة أداة Collectors طريقة toList التي يمكنها إنشاء تطبيق Collector لإضافة عناصر إلى ArrayList. دعونا نعدل الكود السابق ونستخدم طريقة التجميع ().
انسخ رمز الكود كما يلي:
قائمة<شخص> أقدم من 20 =
People.stream()
.filter(person -> person.getAge() > 20)
.collect(Collectors.toList());
System.out.println("الأشخاص الأكبر من 20 عامًا:" + SeniorThan20);
يتم استخدام نسخة مختصرة من طريقة التجميع () لفئة أداة المجمعات، ولكن يمكن استخدامها بأكثر من طريقة. هناك عدة طرق مختلفة في فئة أداة Collectors لإجراء عمليات جمع وإضافة مختلفة. على سبيل المثال، بالإضافة إلى طريقة toList()، هناك أيضًا طريقة toSet()، والتي يمكن إضافتها إلى مجموعة، وطريقة toMap()، والتي يمكن استخدامها للتجميع في مجموعة قيمة المفتاح، وطريقة toMap()، والتي يمكن استخدامها للتجميع في مجموعة قيمة المفتاح، طريقة الانضمام () والتي يمكن تقسيمها إلى سلسلة. يمكننا أيضًا الجمع بين أساليب مثل mapping() وcollectingAndThen() وminBy() وmaxBy() وgroupingBy() للاستخدام.
دعونا نستخدم طريقة groupingBy() لتجميع الأشخاص حسب العمر.
انسخ رمز الكود كما يلي:
Map<Integer, List<Person>>peopleByAge =
People.stream()
.collect(Collectors.groupingBy(Person::getAge));
System.out.println("مجمعة حسب العمر:" +peopleByAge);
ما عليك سوى استدعاء طريقة التجميع () لإكمال التجميع. يقبل groupingBy() تعبير لامدا أو مرجع الأسلوب - وهذا ما يسمى وظيفة التصنيف - ويقوم بإرجاع قيمة سمة معينة للكائن الذي يحتاج إلى تجميع. وفقًا للقيمة التي تُرجعها وظيفتنا، يتم وضع العناصر الموجودة في سياق الاستدعاء في مجموعة معينة. يمكن رؤية نتائج التجميع في الإخراج:
انسخ رمز الكود كما يلي:
مجمعة حسب العمر: {35=[جريج - 35]، 20=[جون - 20]، 21=[سارة - 21، جين - 21]}
وقد تم تجميع الناس حسب العمر.
في المثال السابق قمنا بتجميع الأشخاص حسب العمر. يمكن لمتغير طريقة groupingBy () التجميع حسب شروط متعددة. تستخدم طريقة groupingBy() البسيطة مصنفًا لجمع العناصر. يمكن للمجمع العام groupingBy() تحديد مُجمع لكل مجموعة. بمعنى آخر، ستمر العناصر عبر مصنفات ومجموعات مختلفة أثناء عملية التجميع، كما سنرى أدناه.
بالاستمرار في المثال أعلاه، هذه المرة بدلاً من التجميع حسب العمر، نحصل فقط على أسماء الأشخاص ونصنفهم حسب أعمارهم.
انسخ رمز الكود كما يلي:
Map<Integer, List<String>> nameOfPeopleByAge =
People.stream()
.يجمع(
groupingBy(Person::getAge, mapping(Person::getName, toList())));
System.out.println("الأشخاص مجمعون حسب العمر:" + nameOfPeopleByAge);
يقبل هذا الإصدار من groupingBy() معلمتين: الأول هو العمر، وهو شرط التجميع، والثاني هو المُجمع، وهو النتيجة التي تُرجعها الدالة mapping(). تأتي جميع هذه الأساليب من فئة أداة Collectors ويتم استيرادها بشكل ثابت في هذا الكود. تقبل طريقة التعيين () معلمتين، إحداهما هي السمة المستخدمة لرسم الخرائط، والأخرى هي المكان الذي سيتم جمع الكائنات فيه، مثل القائمة أو المجموعة. دعونا نلقي نظرة على مخرجات الكود أعلاه:
انسخ رمز الكود كما يلي:
الأشخاص مجمعون حسب العمر: {35=[جريج]، 20=[جون]، 21=[سارة، جين]}
كما ترون، تم تجميع أسماء الأشخاص حسب العمر.
دعونا نلقي نظرة على عملية الجمع مرة أخرى: قم بالتجميع حسب الحرف الأول من الاسم، ثم حدد أكبر شخص في كل مجموعة.
انسخ رمز الكود كما يلي:
Comparator<Person> byAge = Comparator.comparing(Person::getAge);
Map<Character, اختياري<Person>> أقدمPersonOfEachLetter =
People.stream()
.collect(groupingBy(person -> person.getName().charAt(0),
تقليل(BinaryOperator.maxBy(byAge)))));
System.out.println("أكبر شخص في كل حرف:");
System.out.println(oldestPersonOfEachLetter);
قمنا أولاً بفرز الأسماء أبجديًا. ولتحقيق ذلك، نقوم بتمرير تعبير لامدا كمعلمة أولى للدالة groupingBy(). يُستخدم تعبير لامدا هذا لإرجاع الحرف الأول من الاسم للتجميع. لم تعد المعلمة الثانية تعيين () ولكنها تنفذ عملية تقليل. داخل كل مجموعة، يستخدم أسلوب maxBy () لاشتقاق العنصر الأقدم من جميع العناصر. يبدو بناء الجملة منتفخًا بعض الشيء نظرًا للعديد من العمليات التي يجمعها، ولكن الأمر برمته يقرأ على النحو التالي: قم بالتجميع حسب الحرف الأول من الاسم، ثم انتقل إلى الأكبر في المجموعة. خذ بعين الاعتبار مخرجات هذا الرمز، الذي يسرد أكبر شخص في مجموعة من الأسماء تبدأ بحرف معين.
انسخ رمز الكود كما يلي:
أكبر شخص من كل حرف:
{S=اختياري[سارة - 21]، G=اختياري[جريج - 35]، J=اختياري[جين - 21]}
لقد اختبرنا بالفعل قوة طريقة التجميع () وفئة الأداة المساعدة Collectors. في التوثيق الرسمي لـ IDE أو JDK، خذ بعض الوقت لدراسة فئة أداة Collectors والتعرف على الطرق المختلفة التي توفرها. بعد ذلك سوف نستخدم تعبيرات لامدا لتنفيذ بعض المرشحات.