الـ Streams في جافا هي طريقة لمعالجة البيانات بشكل كفؤ وأنيق باستخدام ما يُعرف بـ functional programming. ببساطة، تخليك تكتب كود أنظف وأقصر وأسرع عند التعامل مع القوائم والمجموعات.
في أكثر من طريقة لإنشاء stream. الأولى عن طريق list موجودة عندك:
List<String> names = new ArrayList<>(List.of("John", "Sky", "Smith"));
names.stream()
.map(name -> name.toUpperCase())
.forEach(name -> System.out.println(name));
الطريقة الثانية هي باستخدام Stream.of() مباشرة:
Stream.of("John", "Sky", "Smith")
.map(name -> name.toUpperCase())
.forEach
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
النتيجة في الحالتين واحدة - كل الأسماء تطلع بأحرف كبيرة.
الـ Streams عندها ثلاث خصائص أساسية لازم تعرفها.
Functional programming: كل العمليات مبنية على lambda functions، مثل ما شفت بالأمثلة فوق.
Lazy execution: الـ stream ما بيشتغل إلا وقت التنفيذ الفعلي. السبب هو إنه في مرحلتين - intermediate operations وterminal operations - والبرنامج ما بيطبق الـ intermediate operations إلا لما تنتقل على الـ terminal operation.
Immutable: الـ stream ما بيغير الـ variable الأصلي. إذا عندك list وعملت عليها stream وحولت البيانات، الـ list الأصلية بتضل كما هي.
List<String> names = new ArrayList<>(List.of("John", "Sky", "Smith"));
names.stream()
.map(name -> name.toUpperCase())
.forEach(System.out::println);
// names لسه فيها القيم الأصلية بدون تغيير
for (String name : names) {
System.out.println(name); // John, Sky, Smith
}كل stream مقسوم لقسمين:
| النوع | الوظيفة | أمثلة |
|---|---|---|
| Intermediate Operation | العمليات الوسيطة، بتعدل على البيانات | filter(), map(), sorted(), limit(), skip() |
| Terminal Operation | العملية النهائية، بتحول الـ stream لنتيجة | forEach(), toList(), reduce(), count(), anyMatch() |
الـ intermediate operations كلها lazy evaluated - يعني ما بتنفذ إلا لما تصل على terminal operation.
حلاوة الـ intermediate operations إنك فيك تجمع أكثر من واحدة ببعض:
names.stream()
.filter(name -> name.length() > 3)
.skip(1)
.limit(2)
.forEach(System.out::println);كثير ناس بتخربط بينهم، بس الفرق بسيط.
filter() - تعمل فلترة للبيانات. يعني ممكن يكون عندك 3 عناصر وبعد الفلتر يصيروا 2. بتشيل العناصر الغير مرغوبة وبتحتفظ بالباقي.
map() - تغير شكل البيانات من شكل لشكل ثاني. العدد بيضل نفسه، بس البيانات بتتحول. مثل ما تحول مربعات لدوائر - العدد نفسه، بس الشكل تغير.
// filter - بتشيل الأسماء اللي طولها 3 أحرف أو أقل
names.stream()
.filter(name -> name.length() > 3)
.forEach(System.out::println);
// النتيجة: Smith فقط (John و Sky اتشالوا)
// map - بتحول كل الأسماء لأحرف كبيرة
names.stream()
.map(name -> name.toUpperCase())
.forEach(System.out::println);
// النتيجة: JOHN, SKY, SMITHمن ناحية الأداء، الأفضل تحط filter() قبل map() - لأنك بتقلل عدد العناصر قبل ما تعمل عليها تحويل.
sorted() بتعمل ترتيب للبيانات تصاعدياً بشكل افتراضي:
names.stream()
.sorted()
.forEach(System.out::println);
// النتيجة: John, Sky, Smith (مرتبة أبجدياً)limit() و skip() مفيدين كثير للـ pagination:
names.stream()
.filter(name -> name.length() > 3)
.skip(1) // تخطى أول عنصر
.limit(1) // خذ عنصر واحد بس
.forEach(System.out::println);
// النتيجة: Smithبتحول الـ stream لـ List عادية تقدر تشتغل فيها:
List<String> upperNames = names.stream()
.map(name -> name.toUpperCase())
.toList();بتجمع كل البيانات بشيء واحد. مفيدة مثلاً لجمع أرقام:
List<Integer> scores = List.of(80, 100, 180, 40);
int finalScore = scores.stream()
.reduce(0, (accumulator, number) -> accumulator + number);
System.out.println(finalScore); // 400الـ 0 هو القيمة الابتدائية للـ accumulator. كل iteration بياخذ القيمة اللي راكمها حتى هلق ويضيف عليها الرقم الجديد:
بتحسبلك قد إيش في عناصر بالـ stream:
long count = names.stream()
.filter(name -> name.length() > 3)
.count();بترجع true إذا أي عنصر بالـ list حقق الشرط:
List<Integer> scores = List.of(80, 100, 180, 40);
boolean isGreater = scores.stream()
.anyMatch(number -> number > 50);
System.out.println(isGreater); // true لأن 80 و 100 و 180 كلهم أكبر من 50إذا ما لقى أي عنصر يطابق الشرط، بيرجع false.
في كمان allMatch() اللي بتشترط إن كل العناصر تحقق الشرط، وfindFirst() وfindAny() اللي بيلاقوا أول عنصر مطابق.
Intermediate Operations:
filter() - فلترة بناءً على شرطmap() - تحويل شكل البياناتflatMap() - دمج أكثر من stream بواحدةsorted() - ترتيب البياناتdistinct() - إزالة المكرراتlimit() - تحديد عدد العناصرskip() - تخطي عدد من العناصرTerminal Operations:
forEach() - تنفيذ عملية على كل عنصرtoList() - تحويل لـ ListtoArray() - تحويل لـ Arrayreduce() - تجميع كل البيانات بقيمة واحدةcount() - عد العناصرmin() / max() - أصغر أو أكبر قيمةanyMatch() / allMatch() - التحقق من شروطfindFirst() / findAny() - إيجاد عنصرالـ Streams بتخليك تكتب كود أنظف وأكثر قابلية للقراءة. الفكرة الأساسية هي إنك بتسلسل العمليات - من intermediate operations اللي بتعدل البيانات، لـ terminal operation اللي بتحول كل شيء للنتيجة النهائية.