البرمجة الوظيفية في Java: من تعبيرات Lambda إلى Stream API
البرمجة الوظيفية (FP) هي نموذج يتم فيه تمثيل العمليات الحسابية كتقييم للدوال الرياضية، مما يتجنب تغييرات الحالة وتحوير البيانات. لفترة طويلة، بقيت Java لغة موجهة للكائنات بشكل صارم، ولكن مع إصدار Java 8 (2014)، اتخذت خطوة كبيرة نحو البرمجة الوظيفية بإضافة تعبيرات lambda والواجهات الوظيفية و Stream API. اليوم، هذه الأدوات لا غنى عنها لكتابة كود موجز وقابل للقراءة وآمن في التعامل مع الخيوط.
في هذه المقالة، سنقوم بتحليل المفاهيم الأساسية للبرمجة الوظيفية في Java: من أساسيات lambdas إلى معالجة المجموعات المتقدمة باستخدام Stream API. المادة موجهة للمطورين الذين هم على دراية بالفعل ببناء جملة Java الأساسية ولكنهم يرغبون في إتقان الأساليب الحديثة.
1. تعبيرات Lambda والواجهات الوظيفية
تعبير lambda هو دالة مجهولة (دالة بدون اسم) يمكن تمريرها كوسيط أو تخزينها في متغير. في Java، تقوم lambda بتنفيذ واجهة وظيفية — وهي واجهة تحتوي على طريقة مجردة واحدة بالضبط (SAM — Single Abstract Method).
بناء الجملة:
(parameters) -> { body }مثال: بدلاً من إنشاء فئة مجهولة للمقارن:
// Old way (Java 7)Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.length() - s2.length(); }};
// New way (Java 8+)Comparator<String> comparatorLambda = (s1, s2) -> s1.length() - s2.length();الواجهات الوظيفية الرئيسية في Java (الحزمة java.util.function):
Predicate<T>— تتحقق من شرط (طريقةtest).Function<T, R>— تحول كائن من النوع T إلى R (طريقةapply).Consumer<T>— تنفذ إجراءً على كائن (طريقةaccept).Supplier<T>— تزود بكائن بدون معلمات إدخال (طريقةget).
مثال مع Predicate:
Predicate<Integer> isEven = n -> n % 2 == 0;System.out.println(isEven.test(4)); // trueSystem.out.println(isEven.test(5)); // falseمراجع الطرق (Method References) هي ترميز مختصر لـ lambda عندما يتكون الجسم من استدعاء طريقة واحدة:
// Lambdalist.forEach(item -> System.out.println(item));// Method Referencelist.forEach(System.out::println);2. Stream API: العمليات الأساسية
Stream API هي أداة قوية لمعالجة تسلسلات البيانات (المجموعات، المصفوفات، الملفات) بأسلوب تصريحي. Stream لا يخزن البيانات؛ بل يصف فقط خط أنابيب من العمليات. لا يتم تعديل البيانات — كل خطوة تنشئ stream جديد.
إنشاء Stream:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");Stream<String> stream = names.stream(); // sequentialStream<String> parallelStream = names.parallelStream(); // parallelالعمليات الوسيطة (ترجع Stream جديد):
filter(Predicate)— تختار العناصر بناءً على شرط.map(Function)— تحول كل عنصر.sorted()/sorted(Comparator)— الترتيب.distinct()— تزيل التكرارات.limit(n)— تحد من عدد العناصر.
العمليات الطرفية (تنهي الـ stream، ترجع نتيجة):
collect(Collectors.toList())— تجمع في مجموعة.forEach(Con