تعابير لامدا والواجهات الوظيفيّة

مراجعة الواجهات الوظيفية

15 دقيقة الدرس 3 من 13

مراجعة الواجهات الوظيفية

قبل أن تُصبح تعبيرات اللامبدا منطقيةً، تحتاج إلى فهم راسخ لـالواجهات الوظيفية — نظام الأنواع الذي يجعل اللامبدا ممكنةً في Java. يتعمّق هذا الدرس في ماهية الواجهة الوظيفية حقًّا، وسبب حاجة JVM إليها، وكيف يُبقي @FunctionalInterface تصميمك صحيحًا.

ما هي الواجهة الوظيفية؟

الواجهة الوظيفية هي أي واجهة تحتوي على تابع مجرّد واحد بالضبط. هذا التابع المجرّد الوحيد (يُختصر بـSAM) هو "الشكل" الذي يجب أن تملأه اللامبدا. يمكن للواجهة أن تمتلك أي عدد من التوابع الافتراضية والتوابع الساكنة والتوابع الموروثة من java.lang.Object — لكن التوابع المجرّدة فقط هي التي تُحتسب ضمن قيد SAM.

// واجهة وظيفية بسيطة interface Greeter { String greet(String name); // التابع المجرّد الوحيد } // صالحة: التابع الافتراضي لا يُخلّ بقيد SAM interface SmartGreeter { String greet(String name); default String greetLoudly(String name) { return greet(name).toUpperCase(); } }

لأنّ Greeter يحتوي على تابع مجرّد واحد فقط، يمكن للمُترجم تعيين أي لامبدا كـ name -> "Hello, " + name عليه مباشرةً دون غموض.

التوصيف @FunctionalInterface

قدّم Java 8 التوصيف @FunctionalInterface. يؤدّي هذا التوصيف وظيفتين:

  1. توثيق النية — يُشير لكل قارئ بأنّ هذه الواجهة مخصّصة للاستخدام كهدف للامبدا.
  2. تطبيق قيد SAM في وقت الترجمة — إذا أضفت تابعًا مجرّدًا ثانيًا بالصدفة، يرفض المُترجم الواجهة بدلًا من كسر كل لامبدا تستخدمها بصمت.
@FunctionalInterface interface Transformer<T, R> { R transform(T input); // التوابع الافتراضية والساكنة مقبولة static <T> Transformer<T, T> identity() { return t -> t; } }
التوصيف اختياري لكنّه موصى به بشدة. لا تشترطه JVM — أي واجهة SAM يمكنها قبول لامبدا. التوصيف هو حارس في وقت الترجمة يجعل عقدك صريحًا. أضفه دائمًا للواجهات التي تنوي استخدامها كلامبدا.

لماذا تحتاج Java إلى نوع للامبدا

خلافًا للغات مثل Python أو JavaScript، فإنّ Java محكومة بالأنواع الساكنة: يجب أن يكون لكل تعبير نوع معروف في وقت الترجمة. تعبير اللامبدا وحده — x -> x * 2 — لا يملك نوعًا مستقلًا. يستنتج المُترجم النوع من سياق الهدف: الواجهة الوظيفية التي يُسنَد إليها أو يُمرَّر كوسيطة لها.

@FunctionalInterface interface Doubler { int apply(int n); } Doubler d = x -> x * 2; // النوع المستهدف هو Doubler System.out.println(d.apply(5)); // 10

يمكن تعيين تعبير اللامبدا نفسه لواجهات وظيفية مختلفة ما دامت توقيعات التوابع متطابقة:

import java.util.function.IntUnaryOperator; // كلتا الواجهتين لها شكل: int -> int Doubler d1 = x -> x * 2; IntUnaryOperator d2 = x -> x * 2; System.out.println(d1.apply(5)); // 10 System.out.println(d2.applyAsInt(5)); // 10
فضّل واجهات المكتبة القياسية (java.util.function.*) على صياغة واجهاتك الخاصة. تأتي مع JDK ثلاث وأربعون واجهة وظيفية جاهزة — Predicate وFunction وConsumer وSupplier وتغييراتها البدائية — وستستكشفها في الدروس القادمة. تعريف @FunctionalInterface مخصّص يكون منطقيًا فقط حين تحتاج اسمًا أكثر وصفيةً أو توقيعًا يتعامل مع الاستثناءات المفحوصة.

ما يُخلّ بقيد SAM

إضافة تابع مجرّد ثانٍ يحوّل الواجهة فورًا إلى واجهة عادية — لا يمكن استخدامها بعد ذلك كهدف للامبدا:

@FunctionalInterface // خطأ في الترجمة — تابعان مجرّدان interface Broken { String process(String s); int count(String s); // <-- هذا هو المشكل }

رسالة المُترجم واضحة: "Invalid @FunctionalInterface annotation; Broken is not a functional interface." هنا يُثبت التوصيف قيمته.

التوابع المجرّدة من Object لا تُحتسب

كل فئة Java ترث في نهاية المطاف من Object، لذا فإنّ equals وhashCode وtoString متاحة دائمًا. يمكن لواجهة إعادة تصريح هذه التوابع دون انتهاك قيد SAM:

@FunctionalInterface interface Labelled { String label(); // التابع المجرّد الوحيد @Override String toString(); // إعادة تصريح Object#toString — ليس SAM ثانيًا }
لا تخلط بين عدد التوابع المجرّدة وعدد التوابع الكلي. التوابع الافتراضية والساكنة وإعادة تصريح توابع Object لا تُخلّ بقيد SAM. فقط التوابع المجرّدة الإضافية غير الافتراضية وغير المتعلّقة بـObject تُسبّب المشكلة.

قائمة مرجعية سريعة

  • تابع مجرّد واحد بالضبط — وهو SAM. يجب أن تتطابق جميع تعبيرات اللامبدا ومراجع التوابع الموجّهة إلى هذه الواجهة مع توقيعه.
  • أي عدد من التوابع الافتراضية والساكنة — تضيف سلوكًا دون كسر العقد.
  • @FunctionalInterface — يوثّق النية ويوفّر شبكة أمان في وقت الترجمة.
  • التوصيف غير مطلوب من المُترجم، لكنّه ممارسة جيدة يجب اتباعها باستمرار.

الخلاصة

الواجهة الوظيفية هي الجسر بين نظام الأنواع الساكتة في Java وعالم اللامبدا. تابعها المجرّد الوحيد يُحدّد الشكل الذي يجب أن تتطابق معه كل لامبدا مُسنَدة إليها. يُثبّت التوصيف @FunctionalInterface هذا العقد، فيحوّل إضافة تابع مجرّد ثانٍ بالخطأ إلى خطأ في وقت الترجمة بدلًا من مفاجأة في وقت التشغيل. مع رسوخ هذه الأساسيات، ينتقل الدرس التالي إلى Predicate<T> — الواجهة الوظيفية الأولى في JDK للاختبارات المنطقية.