مقدمة
تعد تعبيرات Lambda ميزة جديدة مهمة في Java SE 8. تسمح لك تعبيرات Lambda باستبدال الواجهات الوظيفية بالتعبيرات. يشبه تعبير لامدا الطريقة تمامًا، فهو يوفر قائمة معلمات عادية ونصًا (يمكن أن يكون تعبيرًا أو كتلة تعليمات برمجية) يستخدم هذه المعلمات.
تعمل تعبيرات Lambda أيضًا على تحسين مكتبة المجموعات. يضيف Java SE 8 حزمتين للعمليات المجمعة على بيانات التجميع: حزمة java.util.function وحزمة java.util.stream. يشبه الدفق المكرر تمامًا، ولكن مع العديد من الميزات الإضافية. بشكل عام، تعد تعبيرات وتدفقات lambda أكبر التغييرات منذ إضافة العموميات والتعليقات التوضيحية إلى لغة Java. في هذه المقالة، سنرى قوة تعبيرات لامدا والتدفقات من الأمثلة البسيطة إلى الأمثلة المعقدة.
التحضير البيئي
إذا لم يتم تثبيت Java 8، فيجب عليك تثبيته أولاً قبل أن تتمكن من استخدام lambda وstream (يوصي المترجم بتثبيته في جهاز افتراضي للاختبار). تدعم الأدوات وIDEs مثل NetBeans وIntelliJ IDEA ميزات Java 8، بما في ذلك تعبيرات lambda والتعليقات التوضيحية القابلة للتكرار وملفات التعريف المدمجة وميزات أخرى.
بناء جملة تعبير لامدا
بناء الجملة الأساسي:
(المعلمات) -> التعبير
أو
(المعلمات) ->{ البيانات }
فيما يلي مثال بسيط لتعبير Java lambda:
انسخ رمز الكود كما يلي:
// 1. لا توجد معلمات مطلوبة، قيمة الإرجاع هي 5
() -> 5
// 2. تلقي معلمة (نوع رقمي) وإرجاع ضعف قيمتها
س -> 2*س
// 3. اقبل معلمتين (رقمين) وأرجع الفرق بينهما
(س، ص) -> س ص
// 4. احصل على عددين صحيحين من النوع int وأرجع مجموعهما
(int x, int y) -> x + y
// 5. اقبل كائن سلسلة واطبعه على وحدة التحكم دون إرجاع أي قيمة (يبدو أنه يُرجع فارغًا)
(سلسلة) -> System.out.print(s)
مثال لامدا الأساسي
الآن بعد أن عرفنا ما هي تعبيرات لامدا، فلنبدأ ببعض الأمثلة الأساسية. في هذا القسم، سنرى كيف تؤثر تعبيرات لامدا على طريقة البرمجة. لنفترض أن هناك قائمة من اللاعبين، يمكن للمبرمج استخدام عبارة for ("حلقة for") للاجتياز، والتي يمكن تحويلها إلى نموذج آخر في Java SE 8:
انسخ رمز الكود كما يلي:
String[] atp = {"رافائيل نادال"، "نوفاك ديوكوفيتش"،
"ستانيسلاس فافرينكا"
"ديفيد فيرير"، "روجر فيدرر"،
"آندي موراي"، "توماس بيرديتش"،
"خوان مارتن ديل بوترو"};
List<String> player = Arrays.asList(atp);
// طريقة الحلقة السابقة
لـ (مشغل السلسلة: اللاعبون) {
System.out.print(player + ";");
}
// استخدم تعبيرات لامدا والعمليات الوظيفية
player.forEach((player) -> System.out.print(player + "; "));
// استخدم عامل النقطتين المزدوجتين في Java 8
player.forEach(System.out::println);
كما ترون، يمكن لتعبيرات لامدا اختصار الكود الخاص بنا إلى سطر واحد. مثال آخر موجود في برامج واجهة المستخدم الرسومية حيث يمكن استبدال الفئات المجهولة بتعبيرات لامدا. وبالمثل، يمكن أيضًا استخدامه بهذه الطريقة عند تنفيذ الواجهة القابلة للتشغيل:
انسخ رمز الكود كما يلي:
// استخدم فئة داخلية مجهولة
btn.setOnAction(new EventHandler<ActionEvent>() {
@تجاوز
مقبض الفراغ العام (حدث ActionEvent) {
System.out.println("مرحبا بالعالم!");
}
});
// أو استخدم تعبير لامدا
btn.setOnAction(event -> System.out.println("Hello World!"));
فيما يلي مثال على استخدام lambdas لتنفيذ واجهة Runnable:
انسخ رمز الكود كما يلي:
// 1.1 استخدم الفئات الداخلية المجهولة
موضوع جديد (جديد قابل للتشغيل () {
@تجاوز
تشغيل الفراغ العام () {
System.out.println("مرحبا بالعالم!");
}
}).يبدأ()؛
// 1.2 استخدم تعبير لامدا
new Thread(() -> System.out.println("مرحبا بالعالم!")).start();
// 2.1 استخدم الفئات الداخلية المجهولة
سباق قابل للتشغيل 1 = جديد قابل للتشغيل () {
@تجاوز
تشغيل الفراغ العام () {
System.out.println("مرحبا بالعالم!");
}
};
// 2.2 استخدم تعبير لامدا
Runnable Race2 = () -> System.out.println("مرحبًا بالعالم!");
// اتصل بطريقة التشغيل مباشرة (لم يتم فتح موضوع جديد!)
Race1.run();
Race2.run();
يستخدم تعبير lambda الخاص بـ Runnable تنسيق الكتلة لتحويل خمسة أسطر من التعليمات البرمجية إلى عبارة سطر واحد. بعد ذلك، في القسم التالي سوف نستخدم لامدا لفرز المجموعة.
فرز المجموعات باستخدام Lambdas
في Java، يتم استخدام فئة المقارنة لفرز المجموعات. في المثال أدناه، سنعتمد على اسم اللاعب ولقبه وطول الاسم والحرف الأخير. كما في المثال السابق، نستخدم أولاً فئة داخلية مجهولة للفرز، ثم نستخدم تعبيرات لامدا لتبسيط التعليمات البرمجية لدينا.
في المثال الأول، سنقوم بفرز القائمة حسب الاسم. باستخدام الطريقة القديمة، سيبدو الكود كما يلي:
انسخ رمز الكود كما يلي:
سلسلة[] اللاعبين = {"رافائيل نادال"، "نوفاك ديوكوفيتش"،
"ستانيسلاس فافرينكا"، "ديفيد فيرير"،
"روجر فيدرر"، "آندي موراي"،
"توماس بيرديتش"، "خوان مارتن ديل بوترو"،
"ريتشارد جاسكيه"، "جون إيسنر"}؛
// 1.1 استخدم الفئات الداخلية المجهولة لفرز اللاعبين حسب الاسم
Arrays.sort(players, new Comparator<String>() {
@تجاوز
مقارنة int العامة (سلسلة s1، سلسلة s2) {
العودة (s1.compareTo(s2));
}
});
باستخدام lambdas، يمكن تحقيق نفس الوظيفة باستخدام الكود التالي:
انسخ رمز الكود كما يلي:
// 1.2 استخدم تعبير لامدا لفرز اللاعبين
Comparator<String>sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players,sortByName);
// 1.3 يمكن أيضًا أن يتخذ الشكل التالي:
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));
التصنيفات الأخرى هي على النحو التالي. كما هو الحال في المثال أعلاه، تنفذ التعليمات البرمجية المقارنة من خلال فئات داخلية مجهولة وبعض تعبيرات لامدا:
انسخ رمز الكود كما يلي:
// 1.1 استخدم الفئات الداخلية المجهولة لفرز اللاعبين بناءً على اللقب
Arrays.sort(players, new Comparator<String>() {
@تجاوز
مقارنة int العامة (سلسلة s1، سلسلة s2) {
return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" ")))));
}
});
// 1.2 استخدم تعبير لامدا للفرز حسب اللقب
Comparator<String>sortBySurname = (سلسلة s1، سلسلة s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );
Arrays.sort(players,sortBySurname);
// 1.3 أو هكذا، أتساءل عما إذا كان المؤلف الأصلي قد أخطأ، فهناك الكثير من الأقواس...
Arrays.sort(players, (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ) )
);
// 2.1 استخدم الفئات الداخلية المجهولة لفرز اللاعبين حسب طول الاسم
Arrays.sort(players, new Comparator<String>() {
@تجاوز
مقارنة int العامة (سلسلة s1، سلسلة s2) {
إرجاع (s1.length() - s2.length());
}
});
// 2.2 استخدم تعبير لامدا للفرز حسب طول الاسم
Comparator<String>sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());
Arrays.sort(players,sortByNameLenght);
// 2.3 أو هذا
Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));
// 3.1 استخدم فئة داخلية مجهولة لفرز اللاعبين حسب الحرف الأخير
Arrays.sort(players, new Comparator<String>() {
@تجاوز
مقارنة int العامة (سلسلة s1، سلسلة s2) {
return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
}
});
// 3.2 استخدم تعبير لامدا للفرز حسب الحرف الأخير
Comparator<String>sortByLastLetter =
(سلسلة S1، سلسلة S2) ->
(s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
Arrays.sort(players,sortByLastLetter);
// 3.3 أو هذا
Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
هذا كل شيء، بسيط وبديهي. في القسم التالي، سنستكشف المزيد من إمكانيات lambdas ونستخدمها مع التدفقات.
استخدام Lambdas والجداول
Stream عبارة عن غلاف للمجموعات وعادةً ما يتم استخدامه مع lambda. يمكن أن يدعم استخدام lambda العديد من العمليات، مثل الخريطة، والتصفية، والحد، والفرز، والعد، والحد الأدنى، والحد الأقصى، والمجموع، والتجميع، وما إلى ذلك. وبالمثل، تستخدم التدفقات عمليات كسولة، فهي لا تقرأ جميع البيانات فعليًا، وينتهي بناء الجملة المتسلسل عند مواجهة أساليب مثل getFirst(). في الأمثلة التالية، سنستكشف ما يمكن أن تفعله عمليات lambda وstreams. لقد أنشأنا فئة "الشخص" واستخدمنا هذه الفئة لإضافة بعض البيانات إلى القائمة، والتي سيتم استخدامها لمزيد من عمليات الدفق. الشخص هو مجرد فئة POJO بسيطة:
انسخ رمز الكود كما يلي:
شخص من الدرجة العامة {
سلسلة خاصة الاسم الأول، الاسم الأخير، الوظيفة، الجنس؛
الراتب الخاص والعمر ؛
شخص عام (الاسم الأول للسلسلة، الاسم الأخير للسلسلة، وظيفة السلسلة،
سلسلة الجنس، العمر الداخلي، الراتب الداخلي) {
this.firstName = firstName;
this.lastName = lastName;
this.gender=gender;
this.age = age;
this.job = job;
this.salary = راتب؛
}
// جيتر واضعة
// .
}
بعد ذلك، سنقوم بإنشاء قائمتين، كلاهما يستخدم لتخزين كائنات الشخص:
انسخ رمز الكود كما يلي:
List<Person> javaProgrammers = new ArrayList<Person>() {
{
add(new Person("Elsdon"، "Jaycob"، "Java programming"، "male"، 43، 2000));
add(new Person("Tamsen"، "Brittany"، "Java programming"، "female"، 23، 1500));
add(new Person("Floyd"، "Donny"، "Java programming"، "male"، 33، 1800));
add(new Person("Sindy"، "Jonie"، "Java programming"، "female"، 32، 1600));
add(new Person("Vere"، "Hervey"، "Java programming"، "male"، 22، 1200));
add(new Person("Maude"، "Jaimie"، "Java programming"، "female"، 27، 1900));
add(new Person("Shawn"، "Randall"، "Java Programmer"، "male"، 30، 2300));
add(new Person("Jayden"، "Corrina"، "Java programming"، "female"، 35، 1700));
add(new Person("Palmer"، "Dene"، "Java Programmer"، "male"، 33، 2000));
add(new Person("Addison"، "Pam"، "Java Programmer"، "female"، 34، 1300));
}
};
List<Person> phpProgrammers = new ArrayList<Person>() {
{
add(new Person("Jarrod"، "Pace"، "PHP Programmer"، "male"، 34، 1550));
add(new Person("Clarette"، "Cicely"، "PHP programming"، "female"، 23، 1200));
add(new Person("Victor"، "Channing"، "PHP Programmer"، "male"، 32، 1600));
add(new Person("Tori"، "Sheryl"، "PHP programming"، "female"، 21، 1000));
add(new Person("Osborne"، "Shad"، "PHP Programmer"، "male"، 32، 1100));
add(new Person("Rosalind"، "Layla"، "مبرمج PHP"، "أنثى"، 25، 1300));
add(new Person("Fraser"، "Hewie"، "PHP Programmer"، "male"، 36، 1100));
add(new Person("Quinn"، "Tamara"، "PHP Programmer"، "female"، 21، 1000));
add(new Person("Alvin"، "Lance"، "PHP Programmer"، "male"، 38، 1600));
add(new Person("Evonne"، "Sari"، "PHP Programmer"، "female"، 40، 1800));
}
};
نستخدم الآن التابع forEach لتكرار القائمة أعلاه وإخراجها:
انسخ رمز الكود كما يلي:
System.out.println("أسماء جميع المبرمجين:");
javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
نستخدم أيضًا طريقة forEach لزيادة راتب المبرمج بنسبة 5%:
انسخ رمز الكود كما يلي:
System.out.println("امنح المبرمجين زيادة في الراتب بنسبة 5%:");
Consumer<Person> GiveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());
javaProgrammers.forEach(giveRaise);
phpProgrammers.forEach(giveRaise);
طريقة أخرى مفيدة هي filter()، والتي تسمح لنا بعرض مبرمجي PHP براتب شهري يزيد عن 1400 دولار:
انسخ رمز الكود كما يلي:
System.out.println("إليك مبرمجي PHP براتب شهري يزيد عن 1400 دولار:")
phpProgrammers.stream()
.filter((ع) -> (p.getSalary() > 1400))
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
يمكننا أيضًا تحديد المرشحات ثم إعادة استخدامها لإجراء عمليات أخرى:
انسخ رمز الكود كما يلي:
// تحديد المرشحات
المسند<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> SaarateFilter = (p) -> (p.getSalary() > 1400);
المسند <Person> GenderFilter = (p) -> ("female".equals(p.getGender()));
System.out.println("هذه مبرمجات PHP أكبر من 24 عامًا ولديهن راتب شهري يزيد عن 1400 دولار:");
phpProgrammers.stream()
مرشح(مرشح العمر)
.filter(salaryFilter)
.filter(genderFilter)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
// إعادة استخدام المرشحات
System.out.println("مبرمجات جافا أكبر من 24 عامًا:");
javaProgrammers.stream()
مرشح(مرشح العمر)
.filter(genderFilter)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
استخدم طريقة الحد لتحديد عدد مجموعات النتائج:
انسخ رمز الكود كما يلي:
System.out.println("أول 3 مبرمجين جافا:");
javaProgrammers.stream()
.الحد(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
System.out.println("أفضل 3 مبرمجات جافا:");
javaProgrammers.stream()
.filter(genderFilter)
.الحد(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
ماذا عن الفرز هل يمكننا التعامل معه في الدفق؟ في المثال التالي، سنقوم بفرز مبرمجي Java حسب الاسم والراتب، ونضعهم في قائمة، ثم نعرض القائمة:
انسخ رمز الكود كما يلي:
System.out.println("الفرز حسب الاسم وعرض أفضل 5 مبرمجين جافا:");
List<Person>sortedJavaProgrammers = javaProgrammers
.تدفق()
.sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
.الحد(5)
.collect(toList());
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
System.out.println("فرز مبرمجي جافا حسب الراتب:");
sortedJavaProgrammers = javaProgrammers
.تدفق()
.sorted( (p, p2) -> (p.getSalary() - p2.getSalary())) )
.collect( toList() );
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
إذا كنا مهتمين فقط بالراتب الأدنى والأعلى، فإن ما هو أسرع من اختيار الأول/الأخير بعد الفرز هو طريقتي الحد الأدنى والحد الأقصى:
انسخ رمز الكود كما يلي:
System.out.println("مبرمج جافا الأقل أجرًا:");
شخص بيرس = javaProgrammers
.تدفق()
.min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
.يحصل()
System.out.printf("الاسم: %s %s؛ الراتب: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary())
System.out.println("مبرمج جافا صاحب أعلى راتب:");
شخص الشخص = javaProgrammers
.تدفق()
.max((p, p2) -> (p.getSalary() - p2.getSalary()))
.يحصل()
System.out.printf("الاسم: %s %s؛ الراتب: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())
في المثال أعلاه رأينا كيف تعمل طريقة التجميع. بالاشتراك مع طريقة الخريطة، يمكننا استخدام طريقة التجميع لوضع مجموعة النتائج الخاصة بنا في سلسلة أو مجموعة أو مجموعة شجرة:
انسخ رمز الكود كما يلي:
System.out.println("قم بربط الاسم الأول لمبرمجي PHP في سلسلة:");
String phpDevelopers = phpProgrammers
.تدفق()
.map(الشخص::getFirstName)
.collect(joining(" ; ")); // يمكن استخدامه كرمز مميز في العمليات الإضافية
System.out.println("احفظ الاسم الأول لمبرمجي Java في الإعداد:");
Set<String> javaDevFirstName = javaProgrammers
.تدفق()
.map(الشخص::getFirstName)
.collect(toSet());
System.out.println("احفظ الاسم الأول لمبرمجي Java في TreeSet:");
TreeSet<String> javaDevLastName = javaProgrammers
.تدفق()
.map(الشخص::getLastName)
.collect(toCollection(TreeSet::new));
يمكن أن تكون التدفقات متوازية أيضًا. الأمثلة هي كما يلي:
انسخ رمز الكود كما يلي:
System.out.println("احسب جميع الأموال المدفوعة لمبرمجي Java:");
int TotalSalary = javaProgrammers
.parallelStream()
.mapToInt(p -> p.getSalary())
.مجموع()؛
يمكننا استخدام طريقة SummaryStatistics للحصول على بيانات تلخيصية متنوعة للعناصر الموجودة في الدفق. بعد ذلك، يمكننا الوصول إلى هذه الطرق مثل getMax أو getMin أو getSum أو getAverage:
انسخ رمز الكود كما يلي:
// احسب العدد والحد الأدنى والحد الأقصى والمجموع والمتوسط للأرقام
List<Integer> number = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
إحصائيات IntSummaryStatistics = أرقام
.تدفق()
.mapToInt((x) -> x)
.summaryStatistics();
System.out.println("أكبر رقم في القائمة:" + stats.getMax());
System.out.println("أصغر رقم في القائمة:" + stats.getMin());
System.out.println("مجموع كل الأرقام:" + stats.getSum());
System.out.println("متوسط جميع الأرقام:" + stats.getAverage());
حسنًا، هذا كل شيء، أتمنى أن يعجبك!
تلخيص
في هذه المقالة، تعلمنا طرقًا مختلفة لاستخدام تعبيرات لامدا، بدءًا من الأمثلة الأساسية وحتى الأمثلة الأكثر تعقيدًا باستخدام لامدا والتدفقات. بالإضافة إلى ذلك، تعلمنا أيضًا كيفية استخدام تعبيرات lambda وفئة المقارنة لفرز مجموعات Java.
ملاحظة المترجم: على الرغم من أنها تبدو متقدمة جدًا، إلا أن جوهر تعبير Lambda هو مجرد "سكر بناء الجملة" الذي يستنتجه المترجم ويساعدك على تحويله وتغليفه في تعليمات برمجية عادية، بحيث يمكنك استخدام تعليمات برمجية أقل لتحقيق نفس الوظيفة . أوصي بعدم استخدامه بشكل عشوائي، لأنه يشبه الكود الذي كتبه بعض المتسللين المتقدمين جدًا، فهو موجز، ويصعب فهمه، ويصعب تصحيحه، وسيرغب موظفو الصيانة في توبيخك.