JavaRush /مدونة جافا /Random-AR /تعبيرات لامدا مع الأمثلة

تعبيرات لامدا مع الأمثلة

نشرت في المجموعة
Java هي في البداية لغة موجهة للكائنات بالكامل. باستثناء الأنواع البدائية، كل شيء في Java هو كائن. حتى المصفوفات هي كائنات. مثيلات كل فئة هي كائنات. لا توجد إمكانية واحدة لتعريف دالة بشكل منفصل (خارج الفصل - تقريبًا. ترجمة ). ولا توجد طريقة لتمرير طريقة كوسيطة أو إرجاع نص الطريقة كنتيجة لطريقة أخرى. انها كذلك. لكن هذا كان قبل Java 8. تعبيرات لامدا مع الأمثلة - 1منذ أيام Swing القديمة، كان من الضروري كتابة فئات مجهولة عندما كان من الضروري تمرير بعض الوظائف إلى طريقة ما. على سبيل المثال، هذا ما تبدو عليه إضافة معالج الأحداث:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
نريد هنا إضافة بعض التعليمات البرمجية إلى مستمع حدث الماوس. لقد حددنا فئة مجهولة MouseAdapterوقمنا على الفور بإنشاء كائن منها. بهذه الطريقة، قمنا بتمرير وظائف إضافية إلى ملف addMouseListener. باختصار، ليس من السهل تمرير طريقة (وظيفة) بسيطة في Java من خلال الوسائط. أجبر هذا القيد مطوري Java 8 على إضافة ميزة مثل تعبيرات Lambda إلى مواصفات اللغة.

لماذا تحتاج Java إلى تعبيرات Lambda؟

منذ البداية، لم تتطور لغة Java كثيرًا، باستثناء أشياء مثل التعليقات التوضيحية، والأسماء العامة، وما إلى ذلك. أولاً وقبل كل شيء، ظلت Java دائمًا موجهة نحو الكائنات. بعد العمل مع اللغات الوظيفية مثل JavaScript، يمكن للمرء أن يفهم كيف أن Java موجهة للكائنات بشكل صارم ومكتوبة بقوة. ليست هناك حاجة للوظائف في جافا. لا يمكن العثور عليها بمفردها في عالم Java. في لغات البرمجة الوظيفية، تأتي الوظائف في المقدمة. إنهم موجودون بمفردهم. يمكنك تعيينها للمتغيرات وتمريرها عبر الوسائط إلى وظائف أخرى. تعد JavaScript واحدة من أفضل الأمثلة على لغات البرمجة الوظيفية. يمكنك العثور على مقالات جيدة على الإنترنت تشرح بالتفصيل فوائد JavaScript كلغة وظيفية. تتمتع اللغات الوظيفية بأدوات قوية مثل Closure، والتي توفر عددًا من المزايا مقارنة بالطرق التقليدية لكتابة التطبيقات. الإغلاق عبارة عن دالة مرتبطة ببيئة - جدول يخزن المراجع لجميع المتغيرات غير المحلية للدالة. في Java، يمكن محاكاة عمليات الإغلاق من خلال تعبيرات Lambda. بالطبع، هناك اختلافات بين عمليات الإغلاق وتعبيرات لامدا، وليست صغيرة، ولكن تعبيرات لامدا هي بديل جيد لعمليات الإغلاق. في مدونته الساخرة والمضحكة، يصف Steve Yegge كيف يرتبط عالم Java بشكل صارم بالأسماء (الكيانات والأشياء - تقريبًا. ترجمة. ). إذا لم تكن قد قرأت مدونته، أوصي به. ويصف بطريقة مضحكة ومثيرة للاهتمام السبب الدقيق وراء إضافة تعبيرات Lambda إلى Java. توفر تعبيرات Lambda وظائف إلى Java كانت مفقودة لفترة طويلة. توفر تعبيرات Lambda وظائف للغة تمامًا مثل الكائنات. على الرغم من أن هذا ليس صحيحًا بنسبة 100%، يمكنك أن ترى أن تعبيرات Lambda، رغم أنها ليست عمليات إغلاق، توفر إمكانات مماثلة. في اللغة الوظيفية، تعبيرات لامدا هي وظائف؛ ولكن في Java، يتم تمثيل تعبيرات lambda بواسطة كائنات، ويجب أن ترتبط بنوع كائن محدد يسمى الواجهة الوظيفية. بعد ذلك سوف ننظر إلى ما هو عليه. تصف مقالة ماريو فوسكو "لماذا نحتاج إلى تعبير Lambda في Java" بالتفصيل سبب احتياج جميع اللغات الحديثة إلى إمكانات الإغلاق.

مقدمة إلى تعبيرات لامدا

تعبيرات Lambda هي وظائف مجهولة (قد لا يكون تعريفًا صحيحًا بنسبة 100% لـ Java، ولكنه يجلب بعض الوضوح). ببساطة، هذه طريقة بدون إعلان، أي. بدون معدّلات الوصول، وإرجاع القيمة والاسم. باختصار، تسمح لك بكتابة طريقة واستخدامها على الفور. إنه مفيد بشكل خاص في حالة استدعاء الأسلوب لمرة واحدة، لأنه يقلل من الوقت المستغرق للإعلان عن الطريقة وكتابتها دون الحاجة إلى إنشاء فصل دراسي. عادةً ما تحتوي تعبيرات Lambda في Java على بناء الجملة التالي (аргументы) -> (тело). على سبيل المثال:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
فيما يلي بعض الأمثلة على تعبيرات Lambda الحقيقية:
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

هيكل تعبيرات لامدا

دعونا ندرس بنية تعبيرات لامدا:
  • يمكن أن تحتوي تعبيرات Lambda على 0 أو أكثر من معلمات الإدخال.
  • يمكن تحديد نوع المعلمات بشكل صريح أو يمكن الحصول عليها من السياق. على سبيل المثال ( int a) يمكن كتابتها بهذا الشكل ( a)
  • يتم وضع المعلمات بين قوسين ويتم فصلها بفواصل. على سبيل المثال ( a, b) أو ( int a, int b) أو ( String a،، )int bfloat c
  • إذا لم تكن هناك معلمات، فأنت بحاجة إلى استخدام الأقواس الفارغة. على سبيل المثال() -> 42
  • عندما يكون هناك معلمة واحدة فقط، إذا لم يتم تحديد النوع بشكل صريح، فيمكن حذف الأقواس. مثال:a -> return a*a
  • يمكن أن يحتوي نص تعبير Lambda على 0 تعبير أو أكثر.
  • إذا كان النص يتكون من عبارة واحدة، فلا يجوز وضعها بين قوسين متعرجين، ويمكن تحديد القيمة المرجعة بدون الكلمة الأساسية return.
  • بخلاف ذلك، تكون الأقواس المتعرجة مطلوبة (كتلة من التعليمات البرمجية) ويجب تحديد قيمة الإرجاع في النهاية باستخدام كلمة أساسية return(وإلا سيكون نوع الإرجاع هو void).

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

في Java، واجهات Marker هي واجهات بدون الإعلان عن الأساليب أو الحقول. بمعنى آخر، واجهات الرمز المميز هي واجهات فارغة. وبالمثل، فإن الواجهات الوظيفية هي واجهات تحتوي على طريقة مجردة واحدة معلنة عليها. java.lang.Runnableمثال على الواجهة الوظيفية. تعلن طريقة واحدة فقط void run(). هناك أيضًا واجهة ActionListener- وظيفية أيضًا. في السابق، كان علينا استخدام فئات مجهولة لإنشاء كائنات تنفذ واجهة وظيفية. مع تعبيرات لامدا، أصبح كل شيء أكثر بساطة. يمكن ربط كل تعبير لامدا ضمنيًا ببعض الواجهات الوظيفية. على سبيل المثال، يمكنك إنشاء مرجع لواجهة Runnable، كما هو موضح في المثال التالي:
Runnable r = () -> System.out.println("hello world");
يتم إجراء هذا النوع من التحويل دائمًا بشكل ضمني عندما لا نحدد واجهة وظيفية:
new Thread(
    () -> System.out.println("hello world")
).start();
في المثال أعلاه، يقوم المترجم تلقائيًا بإنشاء تعبير لامدا كتطبيق Runnableللواجهة من مُنشئ الفئة Thread: public Thread(Runnable r) { }. فيما يلي بعض الأمثلة على تعبيرات لامدا والواجهات الوظيفية المقابلة لها:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
يتحقق التعليق التوضيحي @FunctionalInterfaceالذي تمت إضافته في Java 8 وفقًا لمواصفات لغة Java مما إذا كانت الواجهة المعلنة تعمل أم لا. بالإضافة إلى ذلك، يتضمن Java 8 عددًا من الواجهات الوظيفية الجاهزة للاستخدام مع تعبيرات Lambda. @FunctionalInterfaceسوف يلقي خطأ في الترجمة إذا كانت الواجهة المعلنة لا تعمل. فيما يلي مثال على تعريف الواجهة الوظيفية:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
كما يوحي التعريف، يمكن أن تحتوي الواجهة الوظيفية على طريقة مجردة واحدة فقط. إذا حاولت إضافة طريقة مجردة أخرى، فسوف تحصل على خطأ في الترجمة. مثال:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
خطأ
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
خاتمة:
Worker вызван через анонимный класс
Worker вызван через Lambda
قمنا هنا بتعريف الواجهة الوظيفية الخاصة بنا واستخدمنا تعبير لامدا. الطريقة execute()قادرة على قبول تعبيرات لامدا كوسيطة.

أمثلة على تعبيرات لامدا

أفضل طريقة لفهم تعبيرات Lambda هي إلقاء نظرة على بعض الأمثلة: Threadيمكن تهيئة الدفق بطريقتين:
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
يمكن أيضًا إدارة الأحداث في Java 8 من خلال تعبيرات Lambda. فيما يلي طريقتان لإضافة معالج حدث ActionListenerإلى مكون واجهة المستخدم:
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
مثال بسيط لعرض كافة عناصر مصفوفة معينة. لاحظ أن هناك أكثر من طريقة لاستخدام تعبير لامدا. أدناه نقوم بإنشاء تعبير لامدا بالطريقة المعتادة باستخدام بناء جملة السهم، ونستخدم أيضًا عامل النقطتين المزدوجتين (::)، والذي في Java 8 يحول الطريقة العادية إلى تعبير لامدا:
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
في المثال التالي، نستخدم واجهة وظيفية Predicateلإنشاء اختبار وطباعة العناصر التي تجتاز هذا الاختبار. بهذه الطريقة يمكنك وضع المنطق في تعبيرات لامدا والقيام بالأشياء بناءً عليه.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
خاتمة:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
من خلال تعديل تعبيرات Lambda، يمكنك عرض مربع كل عنصر في القائمة. لاحظ أننا نستخدم الطريقة stream()لتحويل قائمة عادية إلى دفق. يوفر Java 8 فئة رائعة Stream( java.util.stream.Stream). يحتوي على العديد من الطرق المفيدة التي يمكنك من خلالها استخدام تعبيرات لامدا. نقوم بتمرير تعبير لامدا x -> x*xإلى الطريقة map()، والذي يطبقه على جميع العناصر الموجودة في الدفق. وبعد ذلك نستخدم forEachلطباعة جميع عناصر القائمة.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
بالنظر إلى القائمة، تحتاج إلى طباعة مجموع مربعات جميع عناصر القائمة. تتيح لك تعبيرات Lambda تحقيق ذلك عن طريق كتابة سطر واحد فقط من التعليمات البرمجية. يستخدم هذا المثال طريقة الالتواء (التخفيض) reduce(). نستخدم طريقة map()لتربيع كل عنصر، ثم نستخدم طريقة reduce()لدمج جميع العناصر في رقم واحد.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

الفرق بين تعبيرات لامدا والفئات المجهولة

والفرق الرئيسي هو استخدام الكلمة الأساسية this. بالنسبة للفئات المجهولة، thisتشير الكلمة الأساسية ' ' إلى كائن من الفئة المجهولة، بينما في تعبير lambda، thisتشير الكلمة الأساسية ' ' إلى كائن من الفئة التي يتم استخدام تعبير lambda فيها. الفرق الآخر هو طريقة تجميعها. تقوم Java بتجميع تعبيرات لامدا وتحويلها إلى privateأساليب فئة. يستخدم هذا تعليمات الاستدعاء الديناميكي ، المقدمة في Java 7 لربط الطريقة الديناميكية. وصف Tal Weiss في مدونته كيف تقوم Java بتجميع تعبيرات lambda في كود بايت

خاتمة

وصف مارك رينهولد (كبير مهندسي Oracle) تعبيرات Lambda بأنها التغيير الأكثر أهمية في نموذج البرمجة الذي حدث على الإطلاق - بل وأكثر أهمية من الأدوية الجنيسة. يجب أن يكون على حق، لأنه... إنها تمنح مبرمجي Java إمكانيات لغة البرمجة الوظيفية التي كان الجميع ينتظرها. إلى جانب الابتكارات مثل طرق الامتداد الافتراضية، تسمح لك تعبيرات Lambda بكتابة تعليمات برمجية عالية الجودة. أتمنى أن يكون هذا المقال قد أعطاك نظرة على Java 8. حظًا سعيدًا :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION