جافا 8 و Lambda Expression

قررت البدأ بسلسلة من مقالات حول Java 8/9 وماجلبته من مميزات وتحسينات للغة والتي غيرت أسلوب التطوير إلي شكل أفضل، ولعلى أكبر تغير دراماتيكي للجافا وفي إصدارها الثامن كان بإقتراض بعض المفاهيم البرمجة الدالية Functional Programming، وبالتحديد كانت بإضافة lambda expression، method refernces و streams سأتحدث عن كل واحدة منهم في مقالة منفصلة ومقالتنا اليوم ستكون حول lambda expression وتطبيقاتها.

(ملاحظة : سأستعمل كلمة دالة كترجمة لmethod في بعض الأحيان رغم إن الترجمة الحرفية ليست دالة لكن تحمل نفس السياق في المفهوم، وبما إن الشرح سيكون حول Functional Programming أو ما يعني البرمجة الدالية، سأضمن ترجمة الأصلية للكلمة بجانب الكلمة العربية لضمان وصول الفكرة إن Function لا تعني Method).

مالحاجة والدافع لإضافة مفاهيم البرمجة الدالية Functional Programming للغة ؟؟
حسنًا كما تري بدأ يصبح هذا المفهوم شائعًا، وايضًا عالم تطوير البرمجيات تغير وتقنيات تحتاج إلي مواكبة تطورات الذي تحدث في وقتنا الحالي، في الماضي في منتصف التسعينات كان قانون مور ساري ولغة جافا كانت يافعة وقوية وكان ماعلينا فعله هو إنتظار بعض سنوات جهاز الحاسب سيصبح ضعف سرعته السابقة.

الان تغير الأمر بعض الحال، فعتاد اليوم أصبح لا يعتمد علي تقليل من كثافة الشرائح من أجل السرعة، وحتى أغلب هواتفنا المحمول تحتوي علي أكثر من نواة في معالجها، مما يعني البرنامج من مفترض عليه العمل علي بيئة متعددة الأنوية، هنا يأتي دور البرمجة الدالية Functional Programming،  التي تركز دوال صرفة “pure”، بمعني إنه تقوم بإرجاع نفس القيمة مقابل القيم المدخلة دون أثار جانبية و مع عدم قابلية للتغيرimmutability التي تسهل العمل علي بيئات متعددة الأنوية.

تعبير لامدا Lambda Expression 

جافا أصبحت تدعم lambda expression ويقصد بها معاملة الmethods كما لو إنها كائن من درجة الأولي first-object class بمعني من ممكن تمريرها كوسائط لبعض الmethods وهذا محور نقاشنا اليوم .

قبل كل شئ ، علينا النقاش حول ماهية الواجهة الدالية functional interface، الواجهة الدالية functional interface هي مجرد واجهة interface مع دالة واحدة مجردة single abstract method (لاحظ هنا إستعملت دالة في إطار ترجمة method في الغالب تحمل نفس المفهوم ونفس الوظيفة لكن لا تخلط بين method و function فهم مختلفان جدا هنا، لم أجد الترجمة المناسبة في هذا السياق ) يعني نقصد بيها كلاس class تقوم بتوظيف أي واجهة interface بتضمين تطبيقاتها لكل methods .

علي السبيل المثال، كما نعلم جافا تملك العديد من الواجهات interfaces لنقل على سبيل المثال واجهة Runnable interface، تحتوي علي single abstract method وتدعي run ، لا تأخذ متغيرات كوسائط arguments ولا ترجع قيمة بمعنى إنها void، وهناك كلاس Thread دالته البنائية constructor method تأخذ Runnable كوسيط وكمثال سنستعمل anonymous inner class لتمريرها لهذا الconstructor method الخاص بي Thread.

  public class Demo {
    public static void main(String[] args){
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("inside runnable using an anonymous inner class");
            }
        }).start();
    }
}

أصبح الأمر مختصر وأجمل من نسخة السابقة والتي قد كنت إعتد علي إستعمالها، بناء الجملة أو لنقل syntax يستعمل السهم لفصل الوسائط arguments، وهنا لم نمرر أي argument ولم نستعمل الأقواس braces بحكم إنه لا داعي لها طالما إنه في سطر واحد،   lambda expression يجب أن يكون النوع الوسائط arguments type والقيمة المرجعة return type متوافقين مع single abstract method في الواجهة وهذا واضحًا عندما إستعملنا واجهة Runnable وذلك تحتوي علي run حيث لا تستقبل arguments و من نوع void بمعني أدق لا ترجع أي قيمة وهذا واضحًا جللًا علي lambda expression، الlambda expression ماهي إلا مجرد تطبيق للدوال methods الواجهة interfaces، ويمكننا أيضًا إسناد lambda experssion للواجهة كما هو موضح هنا

public class Demo {
    public static void main(String[] args){
        Runnable r = ()->System.out.println("inside Thread constructor using lambda");
        new Thread(r).start();
    }
}

يجدر الذكر بإن lambda expression ليست كلاس أو مكتبة في الJava ويمكن فقط إسنادها للfunctional interface references

إسناد lambda expression كما لو إنه توظيف احد الدوال المجردة single abstract method داخل الواجهة، يمكنأن تفكر  أن lambda expression تشبه من ناحية البنيةanonymous inner class، لهذا السبب يجب أن تكون lambda متوافقة مع الدالة المجردة abstract method ونوع وسائطها وreturn type.
المثال الذي أرفقته للشرح كان بسيط بحكم إنه لا يحتوي علي وسائط arguments ولا يقوم بإرجاع قيمة، سأضيف مثال أخر بإستعمال java.io,FilenameFilter interface، من الوثائق تحتوي هذه الواجهة علي single abstract method تدعي accept 

boolean accept(File dir, String name) 

الوسيط File يرمز للمسار حيث يتم إيجاد الملف مراد إيجاده و String وهو خاص بإسم الملف، وهنا توضيح للمثال بإستعمال anonymous inner class

import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;

public class Demo {
    public static void main(String[] args){
        File directory = new File("./src");
        String[] names = directory.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".java");
            }
        });
        System.out.println(Arrays.asList(names));
    }
}

هنا accept method يقوم بإرجاع true في حالة إيجاد ملف ينتهي باللاحقة .java في حالة عدم وجودها يرجع false.


وهنا طريقة أخري بإستعمال lambda expression .

import java.io.File;
import java.util.Arrays;

public class Demo {
    public static void main(String[] args){
        File directory = new File("./src");
        String[] names = directory.list((dir, name) -> name.endsWith(".java"));
        System.out.println(Arrays.asList(names));
    }
}

أصبح البرنامج يستهلك أقل سطور وأكثر وضوح وسهولة لإستعياب الفكرة، هناك شئ غريب ربما ستلاحظه وهو إن الوسائط لا تحمل نوعا خاص بيها وماذا يقصد بيها، في الحقيقة تتم عملية معرفة أنواع الوسائط أثناء الترجمة compile time فالcompiler سيعرف إن list method تعتمد علي FilenameFilter interface ، من المهم إن تكون lambda expression متوافقة مع single abstract method، كما تلاحظ lambda تقوم بإرجاع boolean type وذلك بعد السهم حي ثلاحظ الدالة endsWitdh تقوم بإرجاع true في حالة وجود ملف من لاحقة .java وهذا جليًا في مثالنا السابق حيث accept دالة method من نوع boolean والوسائط التي تحملها الدالة method مثل عدد الوسائط التي أستعملناها في lambda expression.

 يمكنك تضمين نوع المتغير إن أردت ذلك ، فلك الحرية الإختيار بين تضمينها أو عدم تضمينها.

        File directory = new File("./src");
        String[] names = directory.list((File dir,String name) -> name.endsWith(".java"));

وفي حالة إن lambda expression ستتطلب أكثر من السطر أو إضافة أكثر من جملة عليك إستعمال الأقواس braces و إرجاع القيمة بشكل صريح explicit return statement.