جمع ، مجموعات ، جمع ، جامع ، collectos
المجموعة هي واجهة الأجداد لمجموعات Java.
Collections هي فئة أدوات ضمن حزمة Java.Util ، والتي تتضمن طرقًا ثابتة مختلفة لمعالجة مجموعات.
java.util.stream.stream#collection (java.util.stream.collector <؟ super t ، a ، r>) هي وظيفة دفق مسؤولة عن جمع التدفقات.
java.util.stream.collector هي واجهة لجمع الوظائف التي تعلن عن وظائف جامع.
java.util.comParators هي فئة أدوات جامع مع سلسلة من تطبيقات جامع مدمجة.
وظيفة جامع
يمكنك التفكير في تدفقات Java8 كمجموعات مجموعات بيانات خيالية وكسول. وهي تدعم نوعين من العمليات: العمليات الوسيطة (على سبيل المثال مرشح ، خريطة) والعمليات الطرفية (مثل العد ، FindFirst ، foreach ، تقليل). يمكن توصيل العمليات الوسيطة لتحويل دفق إلى آخر. هذه العمليات لا تستهلك التدفقات ، والغرض من ذلك هو إنشاء خط أنابيب. في المقابل ، تستهلك العمليات الطرفية الفصول ، مما يؤدي إلى نتيجة نهائية. Collect هي عملية تخفيض ، تمامًا مثل تقليلها ، يمكن أن تقبل الطرق المختلفة كمعلمات ، وتراكم العناصر في الدفق في نتيجة ملخص. يتم تعريف النهج المحدد من خلال تحديد واجهة جامع جديدة.
جامعي محدد مسبقا
فيما يلي عرض موجز للمجمع المدمج الأساسي. مصدر البيانات المحاكاة كما يلي:
ArrayList النهائي <dish> الأطباق = lists.newarraylist (طبق جديد ("لحم الخنزير" ، خطأ ، 800 ، نوع. صحيح ، 120 ، نوع. آخر) ، طبق جديد ("بيتزا" ، صواب ، 550 ، نوع. آخر) ، طبق جديد ("الجمبري" ، خطأ ، 300 ، نوعالقيمة القصوى ، الحد الأدنى للقيمة ، متوسط القيمة
// لماذا إعادة اختياري؟ ماذا تفعل إذا كان الدفق فارغًا؟ البصرية لها معنى كبير في هذا الوقت اختياري <char> mostcaloriedish = الأطباق. الأطباق. العد الطويل = summaryStatistics.getCount () ؛ int max = summaryStatistics.getMax () ؛ int min = summaryStatistics.getMin () ؛ المبلغ الطويل = summaryStatistics.getSum () ؛
تحتوي هذه المؤشرات الإحصائية البسيطة على وظائف جامع مدمجة ، خاصة بالنسبة لوظائف إلغاء التزامن من النوع الرقمي ، والتي ستكون أقل تكلفة بكثير من تشغيل نوع التغليف مباشرة.
قم بتوصيل جامع
تريد أن تجمع عناصر التيار؟
// قم بتوصيل String مباشرة Join1 = Dishes.Stream (). Map (Dish :: getName) .collect (collectors.oining ()) ؛ // comma string join2 = dishes.stream (). map (shic:
التولست
قائمة <String> names = dishes.stream (). map (dish :: getName) .collect (tolist ()) ؛
قم بتخطيط الدفق الأصلي في دفق عنصر واحد وجمعه كقائمة.
تعويذة
Set <Type> types = dishes.stream (). map (dish :: gettype) .collect (collectors.toset ()) ؛
اجمع النوع كمجموعة ، ويمكنك تكراره.
توماب
خريطة <type ، dish> bytype = dishes.stream (). collect (tomap (dish :: gettype ، d -> d)) ؛
في بعض الأحيان ، قد يكون من الضروري تحويل صفيف إلى خريطة لذاكرة التخزين المؤقت ، مما يسهل عمليات الحسابات والاستحواذ المتعددة. يوفر Tomap وظائف توليد الأساليب K و V. (لاحظ أن العرض التوضيحي أعلاه هو حفرة ، لا يمكنك استخدامها مثل هذا !!! الرجاء استخدام Tomap (وظيفة ، وظيفة ، ثنائي))
ما سبق هو تقريبا هواة الجمع الأكثر استخدامًا ، وهي في الأساس بما يكفي. ولكن كمبتدئ ، يستغرق الفهم وقتًا. لفهم حقًا لماذا يمكن استخدام هذا لجمع ، يجب عليك التحقق من التنفيذ الداخلي. يمكنك أن ترى أن هؤلاء هواة الجمع يعتمد على java.util.stream.collectors.collectorimpl ، وهي فئة تنفيذ من جامع المذكورة في البداية. سوف يتعلم جامع المخصص الاستخدام المحدد لاحقًا.
تخفيض مخصص
الحالات القليلة السابقة هي الحالات الخاصة لعملية التخفيض المحددة بواسطة طريقة تخفيض المصنع. في الواقع ، يمكن استخدام Collectors.Reducing لإنشاء جامع. على سبيل المثال ، ابحث عن المبلغ
Integer TotalCalories = الأطباق.
بالطبع ، يمكنك أيضًا استخدام التقليل مباشرة
اختياري <integer> totalCalories3 = الأطباق.
على الرغم من أنه على ما يرام ، إذا كنت تفكر في الكفاءة ، لا يزال يتعين عليك اختيار ما يلي
int sum = dishes.stream (). maptoint (dish :: getCalories) .sum () ؛
اختر أفضل حل وفقًا للوضع
كما ذكر أعلاه ، توفر البرمجة الوظيفية عادة طرقًا متعددة لأداء نفس العملية. يعد استخدام Collector Collection أكثر تعقيدًا من استخدام APIs لتدفق. الميزة هي أن التجميع يمكن أن يوفر مستوى أعلى من التجريد والتعميم ، ويسهل إعادة استخدامه وتخصيصه.
تتمثل نصيحتنا في استكشاف حلول مختلفة للمشكلة المطروحة قدر الإمكان ، واختر دائمًا أكثرها احترافية ، والتي تعد بشكل عام أفضل قرار من حيث قابلية القراءة والأداء.
بالإضافة إلى تلقي قيمة أولية ، يمكن أن يستخدم التخفيض أيضًا العنصر الأول كقيمة أولية
اختياري <char> mostcaloriedish = dishes.stream () .Collect (تقليل ((D1 ، D2) -> d1.getCalories ()> d2.getCalories ()؟ D1: D2)) ؛
تقليل
استخدام الخفض معقد للغاية ، والهدف هو دمج قيمتين في قيمة واحدة.
static static <T ، U> Collector <T ،؟ ، U> تقليل (هوية U ، الوظيفة <؟ Super T ،؟
أولا ، رأيت 3 جيرسي.
U هو نوع قيمة الإرجاع. على سبيل المثال ، الحرارة المحسوبة في العرض التوضيحي أعلاه ، u هي عدد صحيح.
فيما يتعلق t ، t هو نوع العنصر في الدفق. من وظيفة الوظيفة ، يمكننا أن نعرف أن وظيفة Mapper هي تلقي معلمة T ثم إرجاع النتيجة U. المقابلة مع Dish في العرض التوضيحي.
؟ في منتصف القائمة العامة مع جامع قيمة الإرجاع ، يمثل هذا نوع الحاوية. يحتاج جامع بالطبع إلى حاوية لتخزين البيانات. هنا؟ هذا يعني أن نوع الحاوية غير مؤكد. في الواقع ، الحاوية هنا هي u [].
حول المعلمات:
الهوية هي القيمة الأولية لنوع قيمة الإرجاع ، والتي يمكن فهمها على أنها نقطة انطلاق المتراكم.
MAPPER هي وظيفة الخريطة ، وأهميتها تكمن في تحويل تدفقات الدفق إلى دفق النوع الذي تريده.
OP هي الوظيفة الأساسية ، ووظائفها هي كيفية التعامل مع متغيرين. من بينها ، المتغير الأول هو القيمة التراكمية ، والتي يمكن فهمها على أنها SUM ، والمتغير الثاني هو العنصر التالي الذي يجب حسابه. وبالتالي ، يتم تحقيق التراكم.
هناك أيضًا طريقة تم تحميلها بشكل زائد لحذف المعلمة الأولى ، مما يعني أن المعلمة الأولى في الدفق تستخدم كقيمة أولية.
static static <T> Collector <T ،؟ ، اختياري <T >> تقليل (BinaryOperator <T> OP)
دعونا نلقي نظرة على الفرق بين قيمة الإرجاع. يمثل T قيمة الإدخال ونوع قيمة الإرجاع ، أي نوع قيمة الإدخال ونوع قيمة الإخراج متماثلان. اختلاف آخر اختياري. هذا لأنه لا توجد قيمة أولية ، وقد تكون المعلمة الأولى فارغة. عندما يكون عنصر الدفق فارغًا ، يكون من المفيد للغاية إعادة اختياري.
بالنظر إلى قائمة المعلمات ، يتم ترك فقط ثنائي. BinaryOperator هي واجهة دالة ثلاثية ، والهدف هو حساب معلمتين من نفس النوع وقيم إرجاع نفس النوع. يمكن فهمه على أنه 1> 2؟ 1: 2 ، أي ، ابحث عن القيمة القصوى لرقمين. العثور على أقصى قيمة سهلة الفهم نسبيا. يمكنك تخصيص تعبير Lambda لتحديد قيمة الإرجاع. بعد ذلك ، هنا ، يجب استلام نوع العنصر T من دفقين وإرجاع قيمة الإرجاع من النوع T. من الجيد أيضًا استخدام SUM لفهم.
في العرض التوضيحي أعلاه ، وجد أن وظائف التقليل والتجميع متشابهة تقريبًا ، وكلاهما يعيد النتيجة النهائية. على سبيل المثال ، يمكننا استخدام تقليل تأثير التولست:
. L2) -> {l1.addall (l2) ؛اسمحوا لي أن أشرح الممارسات المذكورة أعلاه.
<u> u تقليل (u Identity ، bifunction <u ،؟ super t ، u> uccumulator ، BinaryOperator <u> combiner) ؛
u هو نوع قيمة الإرجاع ، وهنا قائمة
ثنائي الوظيفة <u ،؟ Super T ، U> Accumulator هو تراكم ، وهدفه هو تجميع القيم وقواعد الحساب للعناصر الفردية. فيما يلي تشغيل القائمة والعناصر ، وأخيراً قائمة العودة. وهذا هو ، إضافة عنصر إلى القائمة.
BinaryOperator <u> Combiner هو combiner ، والهدف هو دمج متغيرين من أنواع قيمة الإرجاع في واحد. هنا هو دمج قائمتين.
هناك مشكلتان في هذا الحل: إحداها مشكلة دلالية والآخر مشكلة عملية. المشكلة الدلالية هي أن طريقة تقليل تهدف إلى الجمع بين قيمتين لتوليد قيمة جديدة ، وهو تخفيض لا يتجزأ. بدلاً من ذلك ، يتمثل تصميم طريقة التحصيل في تغيير الحاوية وتجميع النتائج المراد إخراجها. هذا يعني أن مقتطف الكود أعلاه يسيء استخدام طريقة تقليل لأنه يغير القائمة كمراحى في مكانه. تخلق الدلالات الخاطئة لاستخدام طريقة الحد أيضًا مشكلة عملية: لا يمكن أن يعمل هذا التخفيض بشكل متوازي ، لأن التعديل المتزامن لهيكل البيانات نفسه بواسطة مؤشرات ترابط متعددة قد يدمر القائمة نفسها. في هذه الحالة ، إذا كنت تريد سلامة مؤشر الترابط ، فأنت بحاجة إلى تخصيص قائمة جديدة في وقت واحد ، وسيؤثر تخصيص الكائنات بدوره على الأداء. هذا هو السبب في أن التجميع مناسب للتعبير عن التخفيضات في الحاويات القابلة للتغيير ، والأهم من ذلك ، أنها مناسبة للعمليات المتوازية.
ملخص: تقليل مناسب لتقليل الحاويات غير القابل للتغيير ، والتحصيل مناسب لتقليل الحاويات القابل للتغيير. جمع مناسب للتوازي.
التجميع
غالبًا ما تواجه قاعدة البيانات الحاجة إلى تجميع المجموعة ، وتوفر المجموعة بواسطة Primitive. في جافا ، إذا اتبعت النمط التعليمي (اكتب حلقات يدويًا) ، فسيكون ذلك مرهقة للغاية وعرضة للأخطاء. يوفر Java 8 حلولًا وظيفية.
على سبيل المثال ، مجموعة مجموعة من النوع. على غرار TOMAP السابق ، ولكن قيمة التجميع ليست طبقًا ، ولكنها قائمة.
خريطة <type ، قائمة <char>> dishesbytype = dishes.stream (). collecte (groupingby (dish :: gettype)) ؛
هنا
static public <T ، K> Collector <T ،؟ ، Map <K ، List <T >>> Groupingby (وظيفة <؟ Super T ،؟ تمتد K> المصنف)
مصنف المعلمة هو وظيفة ، مصممة لتلقي معلمة واحدة وتحويلها إلى نوع آخر. العرض التوضيحي أعلاه هو تحويل طبق عنصر الدفق إلى نوع النوع ، ثم قم بتجميع الدفق وفقًا للنوع. يتم تنفيذ تجميعها الداخلي من خلال hashmap. Groupingby (المصنف ، HashMap :: New ، المصب) ؛
بالإضافة إلى التجميع وفقًا لوظيفة خاصية عنصر الدفق نفسه ، يمكنك أيضًا تخصيص أساس التجميع ، مثل التجميع وفقًا لنطاق الحرارة.
نظرًا لأنك تعلم بالفعل أن معلمة Groupingby هي وظيفة ونوع المعلمة من الوظائف هو الطبق ، يمكنك تخصيص المصنف على النحو التالي:
private caloriclevel getCaloriclevel (dish d) {if (d.getCalories () <= 400) {return caloriclevel.diet ؛ } آخر إذا (d.getCalories () <= 700) {return caloriclevel.normal ؛ } آخر {return caloriclevel.fat ؛ }}فقط تمرير في المعلمات
خريطة <caloriclevel ، قائمة <char >> dishesbylevel = dishes.stream () .Collect (Groupingby (this :: getCaloriclevel)) ؛
مجموعة متعددة المستويات
يقوم Groupingby أيضًا بتحميل عدة طرق أخرى ، مثل
static static <T ، K ، A ، D> Collector <T ،؟ ، Map <K ، D >> Groupingby (وظيفة <؟ Super T ،؟ تمتد K> المصنف ، جامع <؟ Super T ، A ، D> المصب)
هناك العديد من الأدوية والأهوال. دعنا نتحصل على فهم موجز. المصنف هو أيضًا مصنف ، يتلقى نوع العنصر من الدفق ويعيد أساسًا تريد تجميعه ، أيد أساس التجميع. لذلك تمثل T نوع العنصر الحالي للتيار ، ويمثل K نوع العنصر للتجميع. المعلمة الثانية هي في اتجاه مجرى النهر ، والمجلس المصب هو جامع جامع. نوع عنصر المجمّع هذا هو فئة فرعية من t ، وحاوية نوع الحاوية هي ، ونوع قيمة الإرجاع للخفض هو D. وهذا يعني ، يتم توفير k للمجموعة من خلال المصنف ، ويتم تقليل قيمة المجموعة من خلال جامع المعلمة الثانية. يحدث فقط أن الكود المصدري للعروض التوضيحية السابقة هو:
static public <T ، K> Collector <T ،؟ ، Map <K ، List <T >>> Groupingby (function <؟ super t ،؟ k> classifier) {return groupingby (classifier ، tolist ()) ؛ }استخدم Tolist كجمع تقليل ، والنتيجة النهائية هي قائمة <char> ، وبالتالي فإن نوع قيمة نهايات المجموعة هو القائمة <dish>. بعد ذلك ، يمكن تحديد نوع القيمة بشكل مماثل من قبل جامع تقليل ، وهناك عشرات الملايين من جامعي تقليل. على سبيل المثال ، أريد تجميع القيمة مرة أخرى ، والتجميع هو أيضًا نوع من الحد.
// خريطة تجميع متعددة المستويات <النوع ، خريطة <caloriclevel ، قائمة <char>> bytypeandcalory = dishes.stream (). جمع (مجموعة (طبق :: gettype ، groupingby (this :: getCaloriclevel))) ؛ System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- System.out.println ("/t" المستوى) ؛نتائج التحقق هي:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [طبق (اسم = لحوم البقر ، نباتي = خطأ ، السعرات الحرارية = 700 ، اكتب = لحوم)] -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- اكتب = آخر)]]
ملخص: المعلمات الأساسية للجماعة هي مولدات K ومولدات V. يمكن أن يكون المولد V أي نوع من جامع جامع.
على سبيل المثال ، يمكن لمولد V حساب الرقم ، وبالتالي تنفيذ العدد المحدد (*) في عبارة SQL من الجدول مجموعة حسب النوع
خريطة <type ، long> typescount = dishes.stream (). جمع (groupingby (dish :: gettype ، counting ())) ؛ system.out.println (typescount) ؛ ---------- {fish = 2 ، other = 3 ، other = 4} SQL Search Group أعلى نقاط select MAX(id) from table A group by Type
خريطة <type ، اختياري <dish >> mostcaloricbytype = dishes.stream () .Collect (groupingby (dish :: gettype ، maxby (comparator.comparingint (dish :: getCalories)))) ؛
اختياري هنا لا معنى له ، لأنه بالتأكيد ليس فارغا. ثم اضطررت إلى إخراجها. باستخدام التحصيل
خريطة <type ، dish> mostcaloricbytype = dishes.stream () .Collect (groupingby (dish :: gettype ، collectingandthen (maxby (comparator.comparingint (shich :: getCalories)) ، اختياري :: get))) ؛
يبدو أن النتيجة تأتي هنا ، لكن الفكرة لا توافق. يجمع الإنذار الأصفر ويغيره إلى:
خريطة <النوع ، طبق> mostcaloricbytype = dishes.stream () .Collect (tomap (dish :: gettype ، function.
نعم ، يصبح Groupingby Tomap ، لا يزال المفتاح نوعًا ، لا تزال القيمة طبقًا ، ولكن هناك معلمة أخرى! ! هنا نرد على الحفرة في البداية. مظاهرة توماب في البداية هي لسهولة الفهم. إذا تم استخدامه بالفعل ، فسيتم قتله. نحن نعلم أن إعادة تنظيم قائمة في خريطة سيواجه حتما نفس المشكلة. عندما يكون k هو نفسه ، هل يتجاوز V أو تجاهله؟ الطريقة التجريبية السابقة هي إدراج K مرة أخرى ورمي استثناء مباشرة عند وجود K:
java.lang.IlegalStateException: طبق مفتاح مكرر (الاسم = لحم الخنزير ، الخضار = خطأ ، السعرات الحرارية = 800 ، اكتب = اللحوم) في java.util.stream.collectors.lambda $ rehrowingmerger $ 0 (collectors.java:133)
الطريقة الصحيحة هي توفير وظائف للتعامل مع النزاعات. في هذا العرض التوضيحي ، يتمثل مبدأ التعامل مع النزاعات في العثور على الأكبر ، والذي يلبي متطلباتنا لتجميع وإيجاد أكبر. (لا أريد حقًا القيام بالتعلم الوظيفي Java 8 ، أشعر أن هناك علبًا من مشاكل الأداء في كل مكان)
تابع تعيين قاعدة البيانات SQL ، select sum(score) from table a group by Type
خريطة <type ، integer> totalCaloriesByType = dishes.stream () .Collect (groupingby (dish :: gettype ، summingInt (shich :: getCalories))) ؛
ومع ذلك ، يتم إنشاء جامع آخر يتم استخدامه في كثير من الأحيان بالتزامن مع Groupingby بواسطة طريقة التعيين. تتلقى هذه الطريقة معلمتين: إحدى الوظائف تقوم بتحويل عناصر في الدفق ، والآخر يجمع كائنات النتيجة المحولة. والغرض من ذلك هو تطبيق وظيفة التعيين على كل عنصر إدخال قبل التراكم ، بحيث يمكن للمجمع الذي يتلقى عناصر من نوع معين التكيف مع أنواع مختلفة من الكائنات. اسمحوا لي أن أنظر إلى مثال عملي لاستخدام هذا جامع. على سبيل المثال ، تريد الحصول على ما هي السعرات الحرارية الموجودة في القائمة لكل نوع من الطبق. يمكننا الجمع بين جمعية المجموعات ورسم الخرائط على النحو التالي:
خريطة <type ، set <Caloriclevel >> caloriclevelsbytype = dishes.stream () .Collect (groupingby (dish :: gettype ، mapping (this :: getCaloriclevel ، toset ()))) ؛
يستخدم TOSET هنا HASHSET افتراضيًا ، ويمكنك أيضًا تحديد التحميل المحدد للتنفيذ (HASHSET :: NEW)
تقسيم
التقسيم هو حالة خاصة للتجميع: يتم استخدام مسند (وظيفة تُرجع قيمة منطقية) كدالة تصنيف ، والتي تسمى وظيفة التقسيم. تُرجع وظيفة القسم قيمة منطقية ، مما يعني أن النوع الرئيسي للخريطة المجمعة هو منطقية ، بحيث يمكن تقسيمها إلى مجموعتين: صواب أو خطأ. على سبيل المثال ، إذا كنت نباتيًا ، فقد ترغب في فصل القائمة بواسطة نباتي وغير نباتي:
خريطة <boolean ، قائمة <char>> positionedmenu = dishes.stream (). collect (partitioningby (dish :: isvegetarian)) ؛
بالطبع ، يمكن أن يحقق استخدام المرشح نفس التأثير:
قائمة <char> VegetarianDishes = الأطباق.
تتمثل ميزة التقسيم في حفظ نسختين ، وهو أمر مفيد عندما تريد تصنيف قائمة. في الوقت نفسه ، مثل Groupingby ، لدى Partitioningby طرقًا زائدة ، والتي يمكن أن تحدد نوع قيمة التجميع.
خريطة <boolean ، خريطة <type ، قائمة <char>> VegetarianDishesByType = dishes.stream () .Collect (partitioningby (ass :: isvegetarian ، groupingby (dish :: gettype)) ؛ map <Ololean ، Integer> VegetariShestotalCalories = dishes.collect. SummingInt (dish :: getCalories)))) ؛ خريطة <boolean ، طبق> mostcaloricpartitionedByvegetarian = dishes.stream () .Collect (partitioningby (dish :: isvegetarian ، collectingandthen (maxby (conpertint (pask :: getCalories)) ، اختياري :: get))) ؛
كمثال الأخير على استخدام Compititioningby Collector ، نضع نموذج بيانات القائمة جانباً لرؤية مثال أكثر تعقيدًا ومثيرة للاهتمام: تقسيم المصفوفات إلى أرقام أولية وغير رئيسية.
أولاً ، حدد وظيفة التقسيم الأولية:
خاص iSprime (المرشح الداخلي) return intstream.rangeClosed (2 ، Candidateroot) .Nonematch (i -> المرشح ٪ i == 0) ؛}
ثم ابحث عن الأرقام الأولية وغير الرائحة من 1 إلى 100
خريطة <boolean ، قائمة <integer >> partitionprimes = intstream.rangeClosed (2 ، 100) .boxed () .Collect (partitioningby (this :: isprime)) ؛