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

من الفئات المجهولة إلى تعبيرات Lambda

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

من الفئات المجهولة إلى تعبيرات Lambda

أتاحت Java دومًا إمكانية تمرير السلوك كـ object. قبل وجود تعبيرات lambda، كانت الطريقة المعتمدة هي الفئة المجهولة (anonymous class) — تنفيذ مضمّن بلا اسم لواجهة (أو فئة مجردة) يُكتب مباشرةً في مكان الاستخدام. تعبيرات lambda لا تضيف مفهومًا جديدًا؛ إنها فقط تزيل الإسهاب المحيط بهذه الفكرة.

نمط الفئة المجهولة

لنفترض أنك تريد ترتيب قائمة من السلاسل النصية حسب الطول. قبل Java 8 كنت ستكتب:

import java.util.Arrays; import java.util.Comparator; import java.util.List; List<String> words = Arrays.asList("banana", "fig", "apple", "kiwi"); words.sort(new Comparator<String>() { @Override public int compare(String a, String b) { return Integer.compare(a.length(), b.length()); } }); System.out.println(words); // [fig, kiwi, apple, banana]

هذا يعمل بشكل مثالي. لكن انظر كم عدد الأسطر اللازمة للتعبير عن فكرة بسيطة واحدة: المقارنة حسب الطول. عليك:

  1. كتابة new Comparator<String>() { } لإنشاء الفئة المجهولة.
  2. كتابة التعليق التوضيحي @Override وتوقيع الدالة الكامل.
  3. ثم — أخيرًا — كتابة السطر الوحيد من المنطق الذي تهتم به فعلًا.

يُسمى هذا النمط أحيانًا الشفرة الزائدة (boilerplate): كود مطلوب هيكليًا لكنه لا يعبّر عن أي نية حقيقية. نسبة الإسهاب إلى المنطق هنا تقريبًا 5:1.

تعبيرات Lambda

تعبير lambda هو طريقة مختصرة لكتابة نفس الفئة المجهولة تمامًا — عندما تحتوي الواجهة على دالة مجردة واحدة فقط. إليك نفس الترتيب مُعاد كتابته بتعبير lambda:

words.sort((a, b) -> Integer.compare(a.length(), b.length()));

سطر واحد. لا تصريح فئة، لا تعليق توضيحي، لا توقيع دالة. يستنتج المُصرِّف أنواع a وb من السياق (المتوقع هو Comparator<String>، لذا كلاهما String).

تعبير lambda ليس نوعًا جديدًا من الكائنات. تحت الغطاء لا يزال الـ JVM ينشئ instance ينفّذ الواجهة المستهدفة. صياغة lambda هي مجرد تدوين مختصر — المعنى مطابق لفئة مجهولة تحتوي على دالة مجردة واحدة.

حالة شائعة أخرى: Runnable

Runnable هي إحدى أقدم الواجهات ذات الدالة الواحدة في Java. مع فئة مجهولة:

Runnable task = new Runnable() { @Override public void run() { System.out.println("Running on a thread"); } }; new Thread(task).start();

مع تعبير lambda:

Runnable task = () -> System.out.println("Running on a thread"); new Thread(task).start();

الأقواس () تشير إلى عدم وجود معاملات. الجسم هو العبارة الواحدة التي تأتي بعد السهم.

ما يراه المُصرِّف

لا يستطيع المُصرِّف استبدال فئة مجهولة بتعبير lambda إلا إذا كانت الواجهة واجهة وظيفية (functional interface) — أي تُعلن عن دالة مجردة واحدة فقط. Comparator وRunnable وعشرات الواجهات الأخرى في JDK تستوفي هذا الشرط. التعليق الاختياري @FunctionalInterface يتيح لك تعريف واجهاتك الخاصة:

@FunctionalInterface interface Greeter { String greet(String name); } // فئة مجهولة Greeter g1 = new Greeter() { @Override public String greet(String name) { return "Hello, " + name + "!"; } }; // lambda مكافئة Greeter g2 = name -> "Hello, " + name + "!"; System.out.println(g1.greet("Alice")); // Hello, Alice! System.out.println(g2.greet("Alice")); // Hello, Alice!

كلا المتغيرين يحملان شيئًا يستجيب لـ greet(String). تعبير lambda هو مجرد طريقة أقصر للتعبير عن ذلك.

لماذا يهمنا هذا

إزالة الإسهاب ليست مجرد تجميل. عندما تختفي الضوضاء، تصبح النية — ما يفعله الكود فعلًا — هي الشيء الوحيد الذي يراه القارئ. هذا التحوّل يفتح باب أسلوب برمجي تُمرَّر فيه أجزاء صغيرة من السلوك وتُجمَّع وتُؤلَّف بنفس سهولة تمرير البيانات. كل درس في هذا البرنامج التعليمي يبني على هذه الفكرة.

ابدأ القراءة من السهم. عندما ترى تعبير lambda، يفصل السهم -> المدخلات (الجانب الأيسر) عن النتيجة (الجانب الأيمن). درّب نفسك على قراءة (a, b) -> Integer.compare(a.length(), b.length()) على أنها "بمعطى a و b، أنتج Integer.compare لطوليهما". الأنواع مجرد ضجيج يتعامل معه المُصرِّف نيابةً عنك.
تعبيرات lambda تعمل فقط مع الواجهات الوظيفية. إذا كانت الواجهة تحتوي على دالتين مجردتين أو أكثر، لا يستطيع المُصرِّف تحديد أيّ منهما ينفّذه جسم lambda وسيرفض التصريف. في هذه الحالة لا تزال بحاجة إلى فئة مجهولة (أو فئة مسمّاة كاملة).

الخلاصة

تتيح الفئات المجهولة تمرير السلوك كـ object لكنها تتطلب قدرًا كبيرًا من الكود الهيكلي الزائد. تعبيرات lambda تعبّر عن نفس الفكرة بصياغة مختصرة (parameters) -> body، شريطة أن يكون النوع المستهدف واجهة وظيفية. يستكشف باقي هذا البرنامج التعليمي المجموعة الغنية من الواجهات الوظيفية التي يوفّرها الـ JDK، والطرق القوية لتأليف تعبيرات lambda وتركيبها.