Consumer و Supplier
Consumer و Supplier
في الدروس السابقة تعرّفت على Predicate (اختبار شيء وإعادة قيمة منطقية) وFunction (تحويل قيمة إلى أخرى). ثمّة واجهتان وظيفيتان مدمجتان أخريان تُكمّلان مجموعة الأدوات اليومية: Consumer وSupplier. إنّهما يقعان في طرفَي نقيض من طيف تدفق البيانات — الأولى تأخذ البيانات فقط دون أن تُعيد شيئًا، والثانية تُنتج البيانات دون أن تأخذ شيئًا.
Consumer — التصرف بناءً على قيمة دون إعادة شيء
تقبل java.util.function.Consumer<T> وسيطًا واحدًا من النوع T ولا تُعيد شيئًا (void). الدالة المجرّدة الوحيدة فيها هي:
استخدم Consumer كلّما أردت تنفيذ تأثير جانبي — طباعة، تسجيل، كتابة في قاعدة بيانات، تحديث واجهة مستخدم — دون إنتاج قيمة جديدة ترجع إلى المُستدعي.
يُترجَم التعبير اللامبدي message -> System.out.println(...) مباشرةً إلى Consumer<String> لأنّه يأخذ وسيطًا واحدًا ولا يُعيد شيئًا. ومرجع الدالة System.out::println يعمل بالطريقة ذاتها وكثيرًا ما يكون أكثر وضوحًا.
تسلسل Consumer عبر andThen
توفّر Consumer دالةً افتراضية andThen(Consumer<? super T> after) تربط بين مستهلكَين كي يعملا على نفس المدخل بالتتابع — مفيد حين تريد التسجيل والحفظ معًا مثلًا.
Function::andThen التي تُمرّر القيمة المُحوَّلة من دالة إلى التالية، فإنّ Consumer::andThen تُغذّي المستهلكَين بـنفس القيمة الأصلية. لا يحدث أي تحويل — كلاهما يعمل على مدخل متطابق.
BiConsumer — مدخلان بلا مخرج
حين تحتاج للتصرف على قيمتين معًا، استخدم BiConsumer<T, U>:
الموقع الأكثر شيوعًا لمصادفة BiConsumer في المكتبة القياسية هو Map::forEach التي تُمرّر كل مفتاح وقيمة إلى تعبيرك اللامبدي.
Supplier — إنتاج قيمة بشكل كسول
لا تأخذ java.util.function.Supplier<T> أيّ وسيطات وتُعيد قيمة من النوع T. الدالة المجرّدة الوحيدة فيها هي:
يُعبّر التوقيع عن النية بوضوح: هذا الكائن هو مصنع أو عملية حسابية مؤجَّلة. تُمرّر Supplier وتستدعي get() فقط حين تحتاج القيمة فعلًا — هذا ما يعنيه التقييم الكسول.
Supplier حين تكون القيمة مكلفة حسابيًا وربّما لن تُحتاج (كرسالة سجلّ احتياطية)، أو حين تريد قيمة جديدة في كل استدعاء لـget() (كمعرّف فريد أو نسخة جديدة من كائن).
مثال عملي — orElseGet مقابل orElse في Optional
تُوضح دالتا الاحتياط في Optional أهمية Supplier على الأداء:
إذا كانت expensiveDefault() تستعلم من قاعدة بيانات أو تستدعي خدمة خارجية، فالفارق بين النهجين قد يكون كبيرًا. فضّل orElseGet مع Supplier كلّما كانت القيمة الاحتياطية غير تافهة.
Supplier ذاكرة. كل استدعاء لـget() يُعيد تنفيذ جسم اللامبدا. إن أردت تخزين النتيجة مؤقتًا، احسبها مرة واحدة واحفظها، أو استخدم غلافًا مخصصًا للتخزين — لا تفترض أن Supplier تُخزّن تلقائيًا.
حقن السلوك في دالة
تتألّق كلتا الواجهتين حين تُضخّ السلوكيات في دالة عبر المعاملات — وهو سمة أسلوب البرمجة الوظيفية:
مرجع سريع
Consumer<T>— تأخذT، لا تُعيد شيئًا. للتأثيرات الجانبية.Consumer::andThen— تسلسل مستهلكَين على نفس المدخل.BiConsumer<T, U>— تأخذTوU، لا تُعيد شيئًا.Supplier<T>— لا تأخذ شيئًا، تُنتجT. للقيم الكسولة أو المؤجَّلة.- فضّل
orElseGet(Supplier)علىorElse(value)للقيم الاحتياطية غير التافهة.
في الدرس القادم ستستكشف مراجع الدوال — اختصار موجز يحوّل الدوال الموجودة مباشرةً إلى أيٍّ من هذه الواجهات الوظيفية.