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

مراجع الأساليب (Method References)

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

مراجع الأساليب (Method References)

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

يُعرّف Java أربعة أنواع من مراجع الأساليب. فهم متى يُطبَّق كلّ نوع — ولماذا يقبله المُصرِّف — هو محور هذا الدرس.

1. مرجع الأسلوب الساكن (Static Method Reference)

الصياغة: ClassName::staticMethod

استخدم هذا النوع حين لا يفعل تعبير اللامبدا سوى استدعاء أسلوب ساكن مع تمرير جميع وسائطه.

import java.util.List; import java.util.function.Function; public class StaticRefDemo { public static int doubleIt(int n) { return n * 2; } public static void main(String[] args) { // النسخة بتعبير اللامبدا Function<Integer, Integer> lambda = n -> doubleIt(n); // مرجع الأسلوب — نفس الشيء، أقل ضجيجًا Function<Integer, Integer> ref = StaticRefDemo::doubleIt; List<Integer> numbers = List.of(1, 2, 3, 4); numbers.stream() .map(StaticRefDemo::doubleIt) .forEach(System.out::println); // 2 4 6 8 } }

يرى المُصرِّف أنّ doubleIt تقبل int واحدًا وتُعيد int واحدًا، وهو ما يطابق توقيع Function<Integer, Integer>. هذا التطابق هو كل ما يحتاجه.

2. مرجع أسلوب نسخة على كائن مُحدَّد

الصياغة: instance::instanceMethod

استخدم هذا النوع حين يكون لديك كائن مُعيَّن في النطاق وتعبير اللامبدا يُمرِّر وسائطه إلى أسلوب على ذلك الكائن.

import java.util.List; import java.util.function.Predicate; public class InstanceRefDemo { public static void main(String[] args) { String prefix = "java"; // النسخة بتعبير اللامبدا Predicate<String> lambda = s -> prefix.startsWith(s); // مرجع الأسلوب على الكائن المُلتقَط 'prefix' Predicate<String> ref = prefix::startsWith; List<String> tokens = List.of("ja", "py", "jav", "c"); tokens.stream() .filter(prefix::startsWith) .forEach(System.out::println); // ja jav } }
الفارق الجوهري: الكائن (prefix) هنا مثبَّت عند لحظة إنشاء المرجع. يتلقّى تعبير اللامبدا وسيطًا واحدًا ويمرّره إلى prefix.startsWith(arg). الكائن هو المستقبِل، وليس وسيطًا.

3. مرجع أسلوب نسخة على نسخة اعتباطية من نوع ما

الصياغة: ClassName::instanceMethod

هذا هو النوع الأصعب. استخدمه حين يتلقّى تعبير اللامبدا نسخةً من فئة ما ويستدعي عليها أسلوبًا — أي أنّ الوسيط الأول يصبح هو المستقبِل.

import java.util.List; import java.util.function.Function; public class ArbitraryInstanceDemo { public static void main(String[] args) { // لامبدا: تستقبل سلسلة نصية وتستدعي toUpperCase() عليها Function<String, String> lambda = s -> s.toUpperCase(); // مرجع الأسلوب — نفس الفكرة: الوسيط هو المستقبِل Function<String, String> ref = String::toUpperCase; List<String> words = List.of("hello", "world"); words.stream() .map(String::toUpperCase) .forEach(System.out::println); // HELLO WORLD } }

حين تكتب String::toUpperCase، يتحقّق المُصرِّف: "هل يأخذ toUpperCase() صفرًا من الوسائط الإضافية ويُعيد String؟" نعم. يُوصَّل عنصر الدفق تلقائيًّا بوصفه المستقبِل.

يعمل هذا أيضًا مع وسائط إضافية. يطابق String::contains النوع BiPredicate<String, CharSequence> لأنّ الوسيط الأول هو المستقبِل والثاني يُمرَّر إلى contains:

import java.util.function.BiPredicate; BiPredicate<String, CharSequence> bp = String::contains; System.out.println(bp.test("functional", "tional")); // true

4. مرجع الباني (Constructor Reference)

الصياغة: ClassName::new

استخدم هذا النوع حين ينشئ تعبير اللامبدا كائنًا جديدًا ويُعيده. يشيع استخدامه مع دوالّ المصنع ومُجمِّعات الدفق.

import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import java.util.function.Function; import java.util.stream.Collectors; public class ConstructorRefDemo { record Point(double x, double y) {} public static void main(String[] args) { // Supplier — باني بلا وسائط Supplier<ArrayList<String>> listFactory = ArrayList::new; ArrayList<String> fresh = listFactory.get(); // Function — باني بوسيط واحد Function<String, StringBuilder> sbFactory = StringBuilder::new; StringBuilder sb = sbFactory.apply("hello"); // داخل دفق: جمع في نوع قائمة مُحدَّد List<String> names = List.of("alice", "bob"); ArrayList<String> result = names.stream() .collect(Collectors.toCollection(ArrayList::new)); System.out.println(result); // [alice, bob] } }
مراجع الباني والأنواع العامة: يعمل ArrayList::new سواء احتجت إلى Supplier<ArrayList<String>> أو Supplier<ArrayList<Integer>> — يستنتج المُصرِّف النوع من السياق.

اختيار النوع المناسب

جدول قرار سريع حين تمتلك تعبير لامبدا وتتساءل هل تستبدله بمرجع أسلوب:

  • يستدعي أسلوبًا ساكنًا مع تمرير جميع الوسائط؟Class::staticMethod
  • يستدعي أسلوبًا على كائن مُلتقَط مُحدَّد؟capturedVar::method
  • يستدعي أسلوبًا على الوسيط الأول للامبدا نفسه؟ParameterType::method
  • ينشئ كائنًا جديدًا؟ClassName::new
تشابه المرجع الساكن ومرجع النسخة الاعتباطية: قد يُشير MyClass::doSomething إلى أسلوب ساكن أو إلى أسلوب نسخة على نسخة اعتباطية — كلاهما يستخدم صياغة متطابقة. يحلّ المُصرِّف الغموض بمطابقة قائمة وسائط الواجهة الوظيفية. إن وُجد كلاهما وطابق كلاهما، تحصل على خطأ تجميع؛ في هذه الحالة أعِد تسمية أحدهما أو استخدم لامبدا صريحة.

الخلاصة

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