مقدمة إلى الواجهات الدالية (Functional Interfaces)
مقدمة إلى الواجهات الدالية (Functional Interfaces)
تعلمت من قبل أن الواجهة تستطيع التصريح بتوابع مجردة تلتزم الفئات المُنفِّذة بتعريفها. الواجهة الدالية هي ببساطة واجهة تصرّح بتابع مجرد واحد بالضبط. هذا القيد الوحيد هو ما يجعلها مميزة: إذ يعني أنك تستطيع تمثيل الواجهة بأكملها بقطعة سلوك واحدة — دالة — ويستطيع Java استبدال تعبير لامبدا موجز في أي مكان يُتوقع فيه تلك الواجهة.
يتناول هذا الدرس المفهوم، والتوصيف @FunctionalInterface، ولماذا تُشكّل هذه الفكرة أساس كود Java الحديث.
قاعدة التابع المجرد الوحيد
انظر إلى هذه الواجهة:
تمتلك Transformer تابعًا مجردًا واحدًا بالضبط، مما يجعلها واجهة دالية. قبل Java 8 كنت تنفّذها بفئة مجهولة:
هذا يعمل، لكنه سبعة أسطر من الكود الزائد للتعبير عن فكرة واحدة: التحويل إلى أحرف كبيرة. تتيح الواجهة الدالية لـ Java اختصار ذلك كليًا.
التوصيف @FunctionalInterface
أضف @FunctionalInterface إلى أي واجهة تنوي الإبقاء عليها كنوع SAM (تابع مجرد وحيد):
@FunctionalInterface حارس وقت الترجمة، وليس تغييرًا في وقت التشغيل. إن أضفت عن طريق الخطأ تابعًا مجردًا ثانيًا يُبلّغ المُترجم فورًا عن خطأ: "Invalid '@FunctionalInterface' annotation; Transformer is not a functional interface". بدون التوصيف تظل الواجهة تعمل كواجهة دالية — التوصيف يجعل نيتك صريحة ومحمية فحسب.
التوابع الافتراضية، والتوابع الساكنة، والتوابع الموروثة من Object (مثل equals أو toString) لا تُحتسب توابع مجردة في هذه القاعدة. تستطيع الواجهة امتلاك ما تشاء منها وتبقى واجهة دالية صالحة:
لماذا هذا مهم: الطريق إلى اللامبدا
الواجهة الدالية هي النوع الهدف الذي يستوفيه تعبير اللامبدا. لا يمتلك Java دوالًا مستقلة — كل قطعة سلوك يجب أن تعيش داخل فئة أو واجهة. الواجهات الدالية هي الآلية التي تُتيح وجود صياغة اللامبدا. حين يرى Java:
يتحقق: ما النوع المتوقع على اليسار؟ Transformer. هل تمتلك Transformer تابعًا مجردًا واحدًا بالضبط؟ نعم — String transform(String). هل يطابق اللامبدا تلك التوقيع (يأخذ String ويعيد String)؟ نعم. تنجح عملية الترجمة ويصبح اللامبدا تنفيذًا لـ transform.
الواجهات الدالية المدمجة في Java
تحتوي حزمة java.util.function على عشرات الواجهات الدالية الجاهزة، لذا نادرًا ما تحتاج إلى كتابة واجهتك الخاصة. الأربعة التي ستراها في كل مكان هي:
Function<T, R>— تأخذTوتعيدR. التابع المجرد:R apply(T t).Predicate<T>— تأخذTوتعيدboolean. التابع المجرد:boolean test(T t).Consumer<T>— تأخذTولا تعيد شيئًا. التابع المجرد:void accept(T t).Supplier<T>— لا تأخذ شيئًا وتعيدT. التابع المجرد:T get().
إليك هذه الواجهات في العمل، جميعها تستخدم اللامبدا كتنفيذات:
تمرير السلوك كمعامل
تظهر القوة الحقيقية حين تقبل واجهة دالية كمعامل لتابع، مما يسمح للمستدعين بحقن أي سلوك يريدونه:
لا يعرف تابع Filter.keep شيئًا عن القاعدة؛ المستدعي يوفّرها كلامبدا. هذا هو نمط الاستراتيجية (Strategy Pattern) معبَّرًا عنه بكلمتين بدلًا من تسلسل فئات.
java.util.function. تغطي Function وPredicate وConsumer وSupplier الغالبية العظمى من الحالات. إدخال أنواع مخصصة يضيف أسماء يجب على المتعاونين تعلّمها دون إضافة تعبيرية حقيقية.
الخلاصة
الواجهة الدالية تمتلك تابعًا مجردًا واحدًا بالضبط. يوثّق التوصيف @FunctionalInterface تلك النية ويضيف فحصًا في وقت الترجمة. لأنه لا يوجد سوى تابع مجرد واحد، يستطيع Java مطابقة تعبير اللامبدا معه تلقائيًا. تُقدّم حزمة java.util.function في Java المعجم المعياري (Function وPredicate وConsumer وSupplier) فنادرًا ما تكتب واجهتك الخاصة. في الدرس القادم سنتعمق في صياغة اللامبدا ونستكشف مراجع التوابع (Method References).