JavaRush /مدونة جافا /Random-AR /استراحة القهوة رقم 177. دليل تفصيلي لـ Java Stream في Jav...

استراحة القهوة رقم 177. دليل تفصيلي لـ Java Stream في Java 8

نشرت في المجموعة
المصدر: Hackernoon يوفر هذا المنشور برنامجًا تعليميًا مفصلاً حول العمل مع Java Stream بالإضافة إلى أمثلة التعليمات البرمجية والشروحات. استراحة القهوة رقم 177.  دليل تفصيلي لـ Java Stream في Java 8 - 1

مقدمة إلى مواضيع جافا في جافا 8

تُستخدم Java Streams، التي تم تقديمها كجزء من Java 8، للعمل مع مجموعات من البيانات. إنها ليست بنية بيانات في حد ذاتها، ولكن يمكن استخدامها لإدخال المعلومات من هياكل البيانات الأخرى عن طريق الطلب والتسلسل لإنتاج نتيجة نهائية. ملحوظة: من المهم عدم الخلط بين Stream وThread، لأنه في اللغة الروسية يُشار إلى كلا المصطلحين غالبًا في نفس الترجمة "flow". يشير الدفق إلى كائن لتنفيذ العمليات (في أغلب الأحيان نقل البيانات أو تخزينها)، في حين يشير الخيط (الترجمة الحرفية - الخيط) إلى كائن يسمح بتنفيذ تعليمات برمجية معينة بالتوازي مع فروع التعليمات البرمجية الأخرى. نظرًا لأن الدفق ليس بنية بيانات منفصلة، ​​فإنه لا يغير مصدر البيانات أبدًا. تحتوي تدفقات Java على الميزات التالية:
  1. يمكن استخدام Java Stream باستخدام الحزمة "java.util.stream". يمكن استيراده إلى برنامج نصي باستخدام الكود:

    import java.util.stream.* ;

    باستخدام هذا الرمز، يمكننا أيضًا تنفيذ العديد من الوظائف المضمنة في Java Stream بسهولة.

  2. يمكن لـ Java Stream قبول الإدخال من مجموعات البيانات مثل المجموعات والمصفوفات في Java.

  3. لا يتطلب Java Stream تغيير بنية بيانات الإدخال.

  4. Java Stream لا يغير المصدر. وبدلاً من ذلك، يقوم بإنشاء مخرجات باستخدام أساليب خطوط الأنابيب المناسبة.

  5. تخضع تدفقات Java للعمليات المتوسطة والنهائية، والتي سنناقشها في الأقسام التالية.

  6. في Java Stream، يتم توجيه العمليات الوسيطة وتحدث بتنسيق تقييم بطيء. تنتهي بالوظائف الطرفية. يشكل هذا التنسيق الأساسي لاستخدام Java Stream.

في القسم التالي، سنلقي نظرة على الطرق المختلفة المستخدمة في Java 8 لإنشاء تدفق Java عند الحاجة.

إنشاء دفق Java في Java 8

يمكن إنشاء سلاسل رسائل Java بعدة طرق:

1. إنشاء دفق فارغ باستخدام طريقة Stream.empty()

يمكنك إنشاء دفق فارغ لاستخدامه لاحقًا في التعليمات البرمجية الخاصة بك. إذا استخدمت التابع Stream.empty() ‎، فسيتم إنشاء دفق فارغ لا يحتوي على أي قيم. يمكن أن يكون هذا الدفق الفارغ مفيدًا إذا أردنا تخطي استثناء المؤشر الفارغ في وقت التشغيل. للقيام بذلك يمكنك استخدام الأمر التالي:
Stream<String> str = Stream.empty();
سيقوم البيان أعلاه بإنشاء دفق فارغ يسمى str دون أي عناصر بداخله. للتحقق من ذلك، ما عليك سوى التحقق من رقم أو حجم الدفق باستخدام المصطلح str.count() . على سبيل المثال،
System.out.println(str.count());
ونتيجة لذلك، نحصل على 0 في الإخراج .

2. قم بإنشاء دفق باستخدام الأسلوب Stream.builder() مع مثيل Stream.Builder

يمكننا أيضًا استخدام Stream Builder لإنشاء دفق باستخدام نمط تصميم المنشئ. إنه مصمم لبناء الكائنات خطوة بخطوة. دعونا نرى كيف يمكننا إنشاء مثيل للدفق باستخدام Stream Builder .
Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Stream<Integer> numStream = numBuilder.build();
باستخدام هذا الكود، يمكنك إنشاء دفق يسمى numStream يحتوي على عناصر int . يتم تنفيذ كل شيء بسرعة كبيرة بفضل مثيل Stream.Builder المسمى numBuilder الذي يتم إنشاؤه أولاً.

3. قم بإنشاء دفق بالقيم المحددة باستخدام طريقة Stream.of ().

هناك طريقة أخرى لإنشاء دفق تتضمن استخدام طريقة Stream.of() . هذه طريقة بسيطة لإنشاء دفق بقيم معينة. يقوم بإعلان وتهيئة مؤشر الترابط أيضًا. مثال على استخدام طريقة Stream.of() لإنشاء دفق:
Stream<Integer> numStream = Stream.of(1, 2, 3);
سيؤدي هذا الكود إلى إنشاء دفق يحتوي على عناصر int ، تمامًا كما فعلنا في الطريقة السابقة باستخدام Stream.Builder . قمنا هنا بإنشاء دفق مباشرةً باستخدام Stream.of() بقيم محددة مسبقًا [1 و 2 و 3] .

4. قم بإنشاء دفق من مصفوفة موجودة باستخدام طريقة Arrays.stream()

تتضمن الطريقة الشائعة الأخرى لإنشاء سلسلة رسائل استخدام المصفوفات في Java. يتم إنشاء الدفق هنا من مصفوفة موجودة باستخدام طريقة Arrays.stream() . يتم تحويل جميع عناصر المصفوفة إلى عناصر دفق. إليك مثال جيد:
Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);
سيقوم هذا الكود بإنشاء numStream يحتوي على محتويات مصفوفة تسمى arr، وهي مصفوفة أعداد صحيحة.

5. دمج تيارين موجودين باستخدام طريقة Stream.concat()

هناك طريقة أخرى يمكن استخدامها لإنشاء دفق وهي طريقة Stream.concat() . يتم استخدامه لدمج خيطين لإنشاء خيط واحد. يتم الجمع بين كلا التدفقين بالترتيب. وهذا يعني أن الخيط الأول يأتي أولاً، يليه الخيط الثاني، وهكذا. مثال على هذا التسلسل يبدو كما يلي:
Stream<Integer> numStream1 = Stream.of(1, 2, 3, 4, 5);

Stream<Integer> numStream2 = Stream.of(1, 2, 3);

Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);
سيقوم البيان أعلاه بإنشاء دفق نهائي يسمى CombinedStream يحتوي على عناصر الدفق الأول numStream1 والدفق الثاني numStream2 واحدًا تلو الآخر .

أنواع العمليات مع Java Stream

كما ذكرنا سابقًا، يمكنك إجراء نوعين من العمليات باستخدام Java Stream في Java 8: المتوسطة والمحطة. دعونا ننظر إلى كل واحد منهم بمزيد من التفصيل.

العمليات الوسيطة

تولد العمليات الوسيطة دفق إخراج ويتم تنفيذها فقط عند مواجهة عملية طرفية. وهذا يعني أن العمليات الوسيطة يتم تنفيذها بتكاسل، ولا يمكن إكمالها إلا من خلال عملية طرفية. ستتعرف على التقييم البطيء والتخطيط بعد قليل. من أمثلة العمليات الوسيطة الطرق التالية: filter() و map() و different() و peek() و sorted() وبعض الطرق الأخرى.

العمليات الطرفية

تكمل العمليات الطرفية تنفيذ العمليات الوسيطة وتقوم أيضًا بإرجاع النتائج النهائية لتدفق الإخراج. نظرًا لأن العمليات الطرفية تشير إلى نهاية التنفيذ البطيء وتوجيه الأنابيب، فلا يمكن استخدام هذا الخيط مرة أخرى بعد خضوعه لعملية طرفية. من أمثلة العمليات الطرفية الطرق التالية: forEach() و collect() و count() و reduce() وما إلى ذلك.

أمثلة على العمليات باستخدام Java Stream

العمليات الوسيطة

فيما يلي بعض الأمثلة على بعض العمليات الوسيطة التي يمكن تطبيقها على Java Stream:

منقي()

تُستخدم هذه الطريقة لتصفية العناصر من الدفق الذي يطابق مسندًا محددًا في Java. تشكل هذه العناصر التي تمت تصفيتها بعد ذلك دفقًا جديدًا. دعونا نلقي نظرة على مثال لفهم أفضل بشكل أفضل.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);
خاتمة:
[98]
Explanation: في هذا المثال، يمكنك أن ترى أنه حتى العناصر (القابلة للقسمة على 2) يتم ترشيحها باستخدام طريقة filter() وتخزينها في قائمة أعداد صحيحة numStream ، والتي تتم طباعة محتوياتها لاحقًا. نظرًا لأن 98 هو العدد الصحيح الوحيد في الدفق، فسيتم طباعته في الإخراج.

خريطة()

تُستخدم هذه الطريقة لإنشاء دفق جديد عن طريق تنفيذ الوظائف المعينة على عناصر دفق الإدخال الأصلي. ربما يحتوي الدفق الجديد على نوع بيانات مختلف. المثال يبدو كالتالي:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);
خاتمة:
[86، 130، 2، 196، 126]
شرح: هنا نرى أنه يتم استخدام طريقة Map() لمضاعفة كل عنصر من عناصر مجرى numStream . كما ترون من المخرجات، تمت مضاعفة كل عنصر من العناصر الموجودة في الدفق بنجاح.

متميز()

يتم استخدام هذه الطريقة لاسترداد العناصر الفردية فقط في الدفق عن طريق تصفية التكرارات. مثال على ذلك يبدو كالتالي:
Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
خاتمة:
[43، 65، 1، 98، 63]
توضيح: في هذه الحالة، يتم استخدام الطريقة المختلفة لـ numStream . يقوم باسترداد كافة العناصر الفردية في numList عن طريق إزالة التكرارات من الدفق. كما ترون من الإخراج، لا توجد تكرارات، على عكس دفق الإدخال، الذي كان يحتوي في البداية على نسختين (63 و1).

نظرة خاطفة ()

تُستخدم هذه الطريقة لتتبع التغييرات المتوسطة قبل تنفيذ العملية الطرفية. هذا يعني أنه يمكن استخدام peek() لإجراء عملية على كل عنصر من عناصر الدفق لإنشاء دفق يمكن تنفيذ المزيد من العمليات الوسيطة عليه.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList()); System.out.println(nList);
خاتمة:
تم تعيينها: 430 تم تعيينها: 650 تم تعيينها: 10 تم تعيينها: 980 تم تعيينها: 630 [430، 650، 10، 980، 630]
Explanation: هنا يتم استخدام طريقة peek() لتوليد نتائج متوسطة حيث يتم تطبيق طريقة Map() على عناصر المجرى. هنا يمكننا أن نلاحظ أنه حتى قبل استخدام العملية الطرفية Collect() لطباعة المحتويات النهائية للقائمة في بيان الطباعة ، تتم طباعة نتيجة تعيين كل عنصر دفق بشكل تسلسلي مسبقًا.

مرتبة()

يتم استخدام الطريقة التي تم فرزها () لفرز عناصر الدفق. بشكل افتراضي، يقوم بفرز العناصر بترتيب تصاعدي. يمكنك أيضًا تحديد ترتيب فرز معين كمعلمة. يبدو مثال تنفيذ هذه الطريقة كما يلي:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
خاتمة:
1 43 ​​​​63 65 98
Explanation: هنا، يتم استخدام الطريقة sorted() لفرز عناصر المجرى بترتيب تصاعدي افتراضيًا (نظرًا لعدم تحديد ترتيب معين). يمكنك أن ترى أن العناصر المطبوعة في الإخراج مرتبة بترتيب تصاعدي.

العمليات الطرفية

فيما يلي بعض الأمثلة على بعض العمليات الطرفية التي يمكن تطبيقها على تدفقات Java:

لكل ()

يتم استخدام طريقة forEach () للتكرار عبر جميع عناصر الدفق وتنفيذ الوظيفة على كل عنصر واحدًا تلو الآخر. يعمل هذا كبديل لعبارات الحلقة مثل for و while وغيرها. مثال:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
خاتمة:
43 65 1 98 63
شرح: هنا يتم استخدام طريقة forEach() لطباعة كل عنصر من عناصر المجرى واحدًا تلو الآخر.

عدد()

يتم استخدام طريقة count() لاسترداد العدد الإجمالي للعناصر الموجودة في الدفق. وهو مشابه للطريقة size() ، والتي تُستخدم غالبًا لتحديد العدد الإجمالي للعناصر في المجموعة. مثال على استخدام طريقة count() مع Java Stream هو كما يلي:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
خاتمة:
5
Explanation: بما أن numStream يحتوي على 5 عناصر صحيحة، فإن استخدام طريقة count() عليه سوف ينتج 5.

يجمع()

تُستخدم طريقة التجميع () لإجراء تخفيضات قابلة للتغيير لعناصر الدفق. ويمكن استخدامه لإزالة المحتوى من الدفق بعد اكتمال المعالجة. يستخدم فئة Collector لإجراء التخفيضات .
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);
خاتمة:
[43، 65، 1، 63]
Explanation: في هذا المثال، تم ترشيح كل العناصر الفردية في المجرى وجمعها/اختزالها في قائمة مسماة بـ odd . وفي النهاية، تتم طباعة قائمة بالأشخاص الغريبين.

الحد الأدنى () والحد الأقصى ()

يمكن استخدام الطريقة min() ، كما يوحي الاسم، في المجرى للعثور على الحد الأدنى من العناصر فيه. وبالمثل، يمكن استخدام طريقة max() للعثور على الحد الأقصى للعنصر في الدفق. دعونا نحاول أن نفهم كيف يمكن استخدامها مع مثال:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);
numStream = Stream.of(43, 65, 1, 98, 63); int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);
خاتمة:
أصغر عنصر: 1 أكبر عنصر: 98
شرح: في هذا المثال، قمنا بطباعة أصغر عنصر في numStream باستخدام طريقة min() والعنصر الأكبر باستخدام طريقة max() . لاحظ أنه قبل استخدام طريقة max() ، قمنا بإضافة عناصر إلى numStream مرة أخرى . وذلك لأن min() هي عملية طرفية وتقوم بتدمير محتويات الدفق الأصلي، وإرجاع النتيجة النهائية فقط (والتي كانت في هذه الحالة العدد الصحيح "الأصغر").

findAny() و findFirst()

يقوم findAny() بإرجاع أي عنصر من عناصر الدفق كـ اختياري . إذا كان الدفق فارغًا، فسيُرجع أيضًا قيمة اختيارية ، والتي ستكون فارغة. تقوم الدالة findFirst() ‎ بإرجاع العنصر الأول من الدفق بالشكل الاختياري . كما هو الحال مع طريقة findAny() ، تقوم طريقة findFirst() أيضًا بإرجاع معلمة اختيارية فارغة إذا كان الدفق المقابل فارغًا. دعونا نلقي نظرة على المثال التالي بناءً على هذه الطرق:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); Optional<Integer> opt = numStream.findFirst();System.out.println(opt); numStream = Stream.empty(); opt = numStream.findAny();System.out.println(opt);
خاتمة:
اختياري[43] اختياري.فارغ
Explanation: هنا، في الحالة الأولى، تقوم طريقة findFirst() بإرجاع العنصر الأول من المجرى كـ اختياري . بعد ذلك، عند إعادة تعيين مؤشر الترابط كمؤشر ترابط فارغ، تقوم طريقة findAny() بإرجاع خيار اختياري فارغ .

allMatch() و AnyMatch() و noneMatch()

يتم استخدام طريقة allMatch() للتحقق مما إذا كانت جميع العناصر في الدفق تتطابق مع مسند معين وترجع القيمة المنطقية true إذا كانت كذلك، وإلا فإنها تُرجع false . إذا كان الدفق فارغًا، فسيتم إرجاعه صحيحًا . يتم استخدام طريقة AnyMatch() للتحقق مما إذا كان أي من العناصر الموجودة في الدفق يتطابق مع مسند معين. وترجع صحيحا إذا كان الأمر كذلك، كاذبة خلاف ذلك. إذا كان الدفق فارغًا، فإنه يُرجع false . تُرجع الدالة noneMatch() ‎ القيمة true إذا لم يتطابق أي عنصر في الدفق مع المسند، وتُرجع القيمة false بخلاف ذلك. مثال لتوضيح ذلك يبدو كما يلي:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); boolean flag = numStream.allMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.anyMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.noneMatch(n -> n==1);System.out.println(flag);
خاتمة:
كاذبة حقيقية كاذبة
Explanation: بالنسبة للمجرى numStream الذي يحتوي على 1 كعنصر، فإن أسلوب allMatch() يقوم بارجاع false لأن جميع العناصر ليست 1، ولكن واحد منها فقط هو 1. تُرجع الطريقة AnyMatch() القيمة true لأن أحد العناصر على الأقل هو 1. تُرجع الطريقة noneMatch() القيمة false لأن 1 موجود بالفعل كعنصر في هذا الدفق.

التقييمات الكسولة في Java Stream

يؤدي التقييم البطيء إلى تحسينات عند العمل مع Java Streams في Java 8. وهي تتضمن بشكل أساسي تأخير العمليات الوسيطة حتى تتم مواجهة العملية الطرفية. التقييم البطيء مسؤول عن منع إهدار الموارد غير الضرورية في العمليات الحسابية حتى تكون النتيجة مطلوبة بالفعل. يتم إنشاء دفق الإخراج الناتج عن العمليات الوسيطة فقط بعد اكتمال العملية الطرفية. يعمل التقييم البطيء مع جميع العمليات الوسيطة في تدفقات Java. يحدث استخدام مفيد جدًا للتقييم البطيء عند العمل مع التدفقات اللانهائية. بهذه الطريقة، يتم منع الكثير من العمليات غير الضرورية.

خطوط الأنابيب في جافا ستريم

يتكون خط الأنابيب في Java Stream من دفق إدخال، وصفر أو أكثر من العمليات الوسيطة المصطفة واحدة تلو الأخرى، وأخيرًا عملية طرفية. يتم تنفيذ العمليات الوسيطة في Java Streams بتكاسل. وهذا يجعل العمليات الوسيطة عبر خطوط الأنابيب أمرًا لا مفر منه. مع خطوط الأنابيب، والتي هي في الأساس عمليات وسيطة يتم دمجها بالترتيب، يصبح التنفيذ البطيء ممكنًا. تساعد خطوط الأنابيب في تتبع العمليات الوسيطة التي يجب تنفيذها بعد مواجهة العملية النهائية في النهاية.

خاتمة

دعونا الآن نلخص ما تعلمناه اليوم. في هذه المقالة:
  1. لقد ألقينا نظرة سريعة على ماهية تدفقات Java.
  2. تعلمنا بعد ذلك العديد من التقنيات المختلفة لإنشاء سلاسل رسائل Java في Java 8.
  3. لقد تعلمنا نوعين رئيسيين من العمليات (العمليات المتوسطة والعمليات الطرفية) التي يمكن إجراؤها على تدفقات Java.
  4. ثم نظرنا بالتفصيل في عدة أمثلة لكل من العمليات المتوسطة والنهائية.
  5. لقد انتهى بنا الأمر إلى تعلم المزيد عن التقييم البطيء والتعرف أخيرًا على خطوط الأنابيب في سلاسل Java.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION