الـ Lambda Expressions في لغة جافا
ماهية الـ Lambda Expression:
هي syntax جديد أضيف على لغة جافا منذ الإصدار الثامن Java SE 8، تختصر عملية كتابة بعض الأكواد الطويلة إلى أكواد أقصر. تساعد الـ Lambda Expression في تمرير كود (أو functionality) إلى الدوال على هيئة parameter (أو بمعنى آخر code as data) على هيئة أكواد مختصرة.
في الإصدار الأقدم من جافا (7 وما قبل)، عندما تريد تمرير block of code ليتم تنفيذه داخل دالة أخرى، عادةً يتم تمرير عيّنة (instance) من كلاس أو interface بداخله دالة واحدة. على سبيل المثال java.lang.Runnable، حيث يحتوي على دالة واحدة اسمها run:
1 2 3 4 |
public interface Runnable { void run(); } |
بحيث يتم إنشاء عيّنة عن طريق إنشاء كلاس يطبق دوال الـ interface:
1 2 3 4 5 6 7 8 |
public class HelloWorldPrinter implements Runnable { @Override public void run() { System.out.println("Hello World"); } } |
وبعد ذلك يتم تمريرها إلى دالة بهذا الشكل:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Main { private static void executeCode(Runnable runnable) { runnable.run(); } public static void main(String[] args) { executeCode(new HelloWorldPrinter()); } } |
ويمكن أيضاً إنشاء عيّنة بشكل مباشر دون استخدام كلاس خارجي باستخدام ما يسمى بـ Anonymous class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Main { private static void executeCode(Runnable runnable) { runnable.run(); } public static void main(String[] args) { executeCode(new Runnable() { @Override public void run() { System.out.println("Hello World"); } }); } } |
يوجد أيضاً interface آخر شبيه بالـ Runnable ولكن يقوم بإرجاع قيمة، ألا وهو java.util.concurrent.Callable
ويمكن أيضاً تعريف interface جديد واستخدامه بنفس الطريقة:
1 2 3 4 |
public interface MathOperation { int calculate(int a, int b); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public class Main { private static int executeMathOperation(int a, int b, MathOperation mathOperation) { return mathOperation.calculate(a, b); } public static void main(String[] args) { int a = 2; int b = 1; int c = executeMathOperation(a, b, new MathOperation() { @Override public int calculate(int a, int b) { return a + b; } }); System.out.println("a + b = " + c); c = executeMathOperation(a, b, new MathOperation() { @Override public int calculate(int a, int b) { return a - b; } }); System.out.println("a - b = " + c); c = executeMathOperation(a, b, new MathOperation() { @Override public int calculate(int a, int b) { return a * b; } }); System.out.println("a x b = " + c); c = executeMathOperation(a, b, new MathOperation() { @Override public int calculate(int a, int b) { return a / b; } }); System.out.println("a ÷ b = " + c); } } |
أما بالنسبة للإصدارات الحديثة (8 أو أحدث)، فيمكن استخدام الـ Lambda Expression حيث تختصر الكود السابق إلى الشكل التالي:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Main { private static int executeMathOperation(int a, int b, MathOperation mathOperation) { return mathOperation.calculate(a, b); } public static void main(String[] args) { int a = 2; int b = 1; int c = executeMathOperation(a, b, (int x, int y) -> {return x + y;}); System.out.println("a + b = " + c); c = executeMathOperation(a, b, (int x, int y) -> {return x - y;}); System.out.println("a - b = " + c); c = executeMathOperation(a, b, (int x, int y) -> {return x * y;}); System.out.println("a x b = " + c); c = executeMathOperation(a, b, (int x, int y) -> {return x / y;}); System.out.println("a ÷ b = " + c); } } |
لاحظ بأنه قمنا باستبدال أسماء المتغيرات الداخلية a و b بـ x و y حتى لا تتعارض مع أسماء المتغيرات الخارجية، حيث أصبح الـ scope للمتغيرات داخل الـ Lambda Expression هو نفسه للمتغيرات داخل الدالة main.
وبما أن الـ lambda expression body يتكون من سطر واحد فقط، فيمكن إزالة الأقواس المحيطة به، بالإضافة إلى إزالة كلمة return وأيضاً الفاصلة المنقوطة ;
أيضاً، يمكن إزالة النوع الموجود داخل الـ lambda expression parameters. سيصبح الكود بهذا الشكل:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Main { private static int executeMathOperation(int a, int b, MathOperation mathOperation) { return mathOperation.calculate(a, b); } public static void main(String[] args) { int a = 2; int b = 1; int c = executeMathOperation(a, b, (x, y) -> x + y); System.out.println("a + b = " + c); c = executeMathOperation(a, b, (x, y) -> x - y); System.out.println("a - b = " + c); c = executeMathOperation(a, b, (x, y) -> x * y); System.out.println("a x b = " + c); c = executeMathOperation(a, b, (x, y) -> x / y); System.out.println("a ÷ b = " + c); } } |
لو أن الـ lambda expression paramters يتكون من متغير واحد فقط، لكان بالإمكان إزالة الأقواس المحيطة به أيضاً.
استخدام الـ lambda expression في لغة الجافا أضاف لها ما يسمى بالبرمجة الوظيفية Functional Programming، حيث يمكننا إعطاء تعريف آخر للـ lambda expression بأنها function لها مدخلات ومخرجات ووظيفة معينة. بمعنى آخر، تعطيها س من المدخلات ثم تقوم ببعض العمليات عليها وتنتج لنا ص من المخرجات، حيث أن س ≥ 0 و ص = 0 أو 1.
تركيبة الـ Lambda Expression:
ببساطة يتكون الـ Lambda Expression في الـ syntax الخاص بلغة الجافا من 3 عناصر أساسية:
- Parameters: وتمثل المدخلات.
- الرمز <-
- Body: وهو block of code، والقيمة المرجعة منه هي المخرجات.
أمثلة لـ Lambda Expression صالحة:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
() -> {} // No parameters; result is void () -> 42 // No parameters, expression body () -> null // No parameters, expression body () -> { return 42; } // No parameters, block body with return () -> { System.gc(); } // No parameters, void block body () -> // Complex block body with returns { if(true) return 12; else { int result = 15; for(int i = 1; i < 10; i++) result *= i; return result; } } (int x) -> x+1 // Single declared-type parameter (int x) -> { return x+1; } // Single declared-type parameter (x) -> x+1 // Single inferred-type parameter x -> x+1 // Parentheses optional for single inferred-type parameter (String s) -> s.length() // Single declared-type parameter (Thread t) -> { t.start(); } // Single declared-type parameter s -> s.length() // Single inferred-type parameter t -> { t.start(); } // Single inferred-type parameter (int x, int y) -> x+y // Multiple declared-type parameters (x, y) -> x+y // Multiple inferred-type parameter |
أمثلة لـ Lambda Expression غير صالحة:
1 2 3 |
(x, int y) -> x+y // Illegal: can't mix inferred and declared types (x, final y) -> x+y // Illegal: no modifiers with inferred types |
مصطلح الـ Functional Interface:
ابتداءً من جافا 8، أصبح هناك مصطلح جديد يسمى بالـ functional interface، وهو أي interface يتكون من دالة واحدة1. وقد تم إنشاء annotation جديد java.lang.FunctionalInterface يضاف على هذا النوع من الـ interface يستخدم فقط من قِبل الـ compiler لكي يتم التأكد بأن هذا الـ interface هو فعلاً functional interface وإلا سيحدث compilation error. بالإضافة لذلك، يظهر تعريف في صفحة الـ javadoc بأن هذا الـ interface هو functional interface.
1: لا يتم احتساب الدوال المطابقة في الـ signature لإحدى دوال الكلاس Object. مثال لهذه الدالة هي الدالة equals الموجودة في الكلاس java.util.Comparator. أيضاً لا يتم احتساب static methods ولا default methods واللتان أضيفتا في جافا 8.
مثال لاستخدام FunctionalInterface@:
1 2 3 4 5 |
@FunctionalInterface public interface MathOperation { int calculate(int a, int b); } |
وهنا يمكننا الاستنتاج بأنه يمكن إنشاء عيّنة من أي functional interface باستخدام الـ Lambda Expression حسب الصورة التالية:
مع ملاحظة بأنه يمكن إزالة ParamterType من الـ lambda expression وأيضاً يمكن إزالة الأقواس إذا كان هناك parameter واحد فقط. بالنسبة للـ lambda expression body، يمكن إزالة الأقواس وكلمة return والفاصلة المنقوطة ; في حالة كان الكود سطر واحد فقط.
ملاحظة أخيرة: يمكن إسناد lambda expression إلى متغير من نوع functional interface بالشكل التالي:
1 2 |
Runnable runnable = () -> System.out.println("Hello World"); runnable.run(); |
ما هو الـ Method Reference؟
من الإضافات الجميلة التي أضيفت إلى جافا 8 هي method reference، وهو نوع آخر من العبارات (بجانب الـ lambda expression) التي تقبل أن يتم إسندها إلى متغير من نوع functional interface. يأتي الـ method reference في 4 أشكال:
- SomeClass::staticMethod وهو مرجع لدالة static من كلاس معين.
- object::instanceMethod وهو مرجع لدالة instance لعيّنة من كلاس معين.
- SomeClass::instanceMethod وهو مرجع لدالة instance من كلاس معين.
- SomeClass::new وهو مرجع لدالة البناء (Constructor).
مثال على الشكل الأول:
1 |
Math::pow // same as (x, y) -> Math.pow(x, y) |
مثال على الشكل الثاني:
1 2 3 |
System.out::println // same as "x -> System.out.println(x)" this::someMethod // same as "() -> this.someMethod()" or "x -> this.someMethod(x)" or ... super::superMethod // same as "() -> super.superMethod()" or "x -> super.superMethod(x)" or ... |
مثال على الشكل الثالث:
1 2 |
String::equalsIgnoreCase // same as (x, y) -> x.equalsIgnoreCase(y) SomeClass::instanceMethodWith2Params // same as (x, y, z) -> x.instanceMethodWith2Params(y, z) |
مثال على الشكل الرابع:
1 |
SomeClass::new // same as () -> new SomeClass() |
توجد حالة خاصة من الشكل الرابع وهي لإنشاء مصفوفة. مثال:
1 |
SomeClass[]::new // same as i -> new SomeClass[i] |
التعليقات (3)
أضف تعليقك
أداة البحث
التصنيفات
- أمن معلومات (1)
- برمجة (7)
شكرا لطرحك لهذا الجانب الجديد في جافا لكنك عرضت الجانب السطحي لقدوم (lamda)
المراد منها ليس اختصار الكود كما نبهت الى ذلكك و انما هي نقلة نوعيه نحو برنمجه جديده (functional paradigm)
يمكنني ان اشرح هذه الفكره التي تجعل لغه جافا صالحه للمستقبل و قادره ان تنافس لغات اخرى
وسترى ان اللامدا ستقود الى الحديث عن Imperative vs Functional Programming
ارجو ان ننتبه الى ان ظهور اللامدا في لغه جافا 8 يفرض علينا هندسة البرامج بتفكير جديد تكون من نتائجه كود مختصر لكن الاهم ان JVM تصاحب اكثر المبرمج (developpers)
a great job Mr. Fouad . keep up
جزاك الله خيرا وأنعم عليك