4. دوال لامدا في Java 8
4.1. المثال-01 - الواجهات الوظيفية ولامبدا
![]() |
لننظر إلى الكود التالي:
package dvp.java8.lambdas;
public class Exemple01 {
public static void main(String[] args) {
// فئات مجهولة
I1 ia1 = new I1() {
@Override
public void doSomething() {
System.out.println("ia1.doSomething");
}
};
I2 ia2 = new I2() {
@Override
public String getSomething(double value) {
return String.format("ia2.getSomething(%s)", value);
}
};
// اللامبدا
I1 ib1 = () -> System.out.println("ib1.lambda");
I2 ib2 = (value) -> String.format("ib2.lambda(%s)", value);
I1 ib3 = () -> {
System.out.println("ib3.lambda");
};
I2 ib4 = (double value) -> {
return String.format("ib4.lambda(%s)", value);
};
// التطبيق
ia1.doSomething();
System.out.println(ia2.getSomething(4.3));
ib1.doSomething();
System.out.println(ib2.getSomething(5.8));
ib3.doSomething();
System.out.println(ib4.getSomething(10.1));
}
}
@FunctionalInterface
interface I1 {
void doSomething();
}
@FunctionalInterface
interface I2 {
String getSomething(double value);
}
- الأسطر 41-44: تُعرّف واجهة وظيفية I1. الواجهة الوظيفية هي واجهة تحتوي على طريقة واحدة فقط. وهي غير مرتبطة بوجود التعليق التوضيحي [@FunctionalInterface] في السطر 41، والذي يُعد اختياريًا؛
- الأسطر 46-49: واجهة وظيفية ثانية I2؛
- الأسطر 6-19: يتم تنفيذ الواجهتين I1 و I2 باستخدام فئات مجهولة، وهو الحل الأكثر شيوعًا قبل إدخال دوال لامدا؛
- الأسطر 21-29: تم تنفيذ الواجهتين I1 و I2 باستخدام دوال لامدا؛
- الأسطر 7-12: تنفيذ الواجهة I1 باستخدام فئة مجهولة. صيغة تنفيذ واجهة I باستخدام فئة مجهولة هي كما يلي:
حيث m1، m2، ... هي الطرق المحددة بواسطة الواجهة I.
- الأسطر 14-19: تنفيذ الواجهة I2 باستخدام فئة مجهولة؛
- السطر 22: تنفيذ الواجهة I1 باستخدام دالة لامدا. نستفيد هنا من حقيقة أن الواجهة الوظيفية تحتوي على طريقة واحدة فقط. تقوم دالة لامدا عندئذ بتنفيذ هذه الطريقة الوحيدة M. وصيغتها هي كما يلي:
يمكن حذف الأنواع T1 و T2 و Tn إذا كان بإمكان المُجمِّع استنتاجها من السياق (استنتاج النوع).
- السطر 22: تنفيذ الطريقة [I1.doSomething] ذات التوقيع:
void doSomething();
[doSomething] هي طريقة لا تحتوي على أي معلمات ولا تُرجع أي نتيجة. يمكن كتابة تنفيذها باستخدام لامدا كما في السطر 22 أو كما في الأسطر 24-26، أي أنه يمكن وضع أقواس معقوفة حول كود دالة لامدا. إذا كان هذا الكود لا يحتوي إلا على تعليمة واحدة كما هو الحال هنا، فيمكن حذف هذه الأقواس المعقوفة؛
- السطر 23: تنفيذ الدالة [I1.getSomething] ذات التوقيع:
String getSomething(double value);
تقبل [getSomething] معلمة من النوع [double] وتُرجع نتيجة من النوع [String]. يمكن أن يكون تنفيذها باستخدام لامدا هو ذلك الموجود في السطر 23 أو ذلك الموجود في الأسطر 27-29. في التنفيذ الموجود في السطر 23:
- يتم حذف نوع المعلمة [value]. وبالتالي، سيتم استخدام النوع [double] الموجود في توقيع [getSomething]؛
- لم يتم وضع رمز لامدا بين أقواس. وبالتالي، فإن نتيجة لامدا هي قيمة التعبير الوحيد في هذا الرمز، وهو هنا: String.format("ib2.lambda(%s)", value)؛
في التنفيذ الوارد في الأسطر 27-29:
- يتم الإعلان صراحةً عن نوع المعلمة [value]؛
- يُستخدم [return] لعرض نتيجة دالة لامدا. في هذه الحالة، يجب استخدام الأقواس المتعرجة؛
- الأسطر 32-37: يتم استدعاء الدوال المجهولة ودوال لامدا المختلفة؛
والنتيجة التي تم الحصول عليها هي كما يلي:
4.2. المثال-02 - الواجهة الوظيفية Predicate<T>
![]() |
في معظم الأحيان، نتعامل مع واجهات وظيفية لمكتبات بدلاً من واجهات وظيفية نحددها بأنفسنا. هنا، نركز على الواجهة الوظيفية [Predicate] المُعرَّفة في الحزمة [java.util.function] التي تضم معظم الواجهات الوظيفية في Java 8. وقد تم تعريفها على النحو التالي:

لقد ذكرنا أن الواجهة الوظيفية لا تحتوي إلا على طريقة واحدة. لكن هنا، توجد أربع طرق. ومن الابتكارات الأخرى التي أدخلتها Java 8 مفهوم الطريقة الافتراضية للواجهة، والتي تتميز بالكلمة الرئيسية [default]. لدينا هنا ثلاث طرق من هذا النوع. وتتميز هذه الطرق بوجود تنفيذ افتراضي لها. وبالتالي، لا توجد أي إلزامية على الفئة التي تُنفِّذ واجهة تحتوي على طرق افتراضية بأن تُنفِّذ هذه الطرق. وبذلك، فإن الفئة التي ترغب في تنفيذ الواجهة [Predicate] ليس عليها سوى تنفيذ طريقة واحدة بشكل إلزامي: الطريقة [test]. وبالتالي، فإن الواجهة [Predicate] هي بالفعل واجهة وظيفية. يمكننا القول إذن إن الواجهة الوظيفية هي واجهة لا يتضمن تنفيذها سوى طريقة واحدة إلزامية. وإذا كانت تحتوي على أكثر من طريقة واحدة، فيجب أن تحمل الطرق الأخرى الكلمة المفتاحية [default].
تستقبل الطريقة [Predicate<T>.test] معلمة من النوع T وتُرجع قيمة منطقية. تُستخدم هذه الواجهة عادةً لتصفية المجموعات. لتوضيح استخدامها، سنستخدم البيانات التالية:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Personne {
public enum Sexe {HOMME,FEMME};
// البيانات
private String nom;
private int age;
private double poids;
private Sexe sexe;
// منشئات
public Personne() {
}
public Personne(String nom, Sexe sexe, int age, double poids) {
this.nom = nom;
this.sexe=sexe;
this.age = age;
this.poids = poids;
}
// الوصول إلى القيم والتعيين
...
// toString
@Override
public String toString(){
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- الأسطر 32-38: تُرجع الطريقة [toString] السلسلة jSON الخاصة بالشخص؛
تُعرّف الفئة [Personnes] قائمة تضم 3 أشخاص:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
public class Personnes {
private static List<Personne> personnes = Arrays.asList(new Personne("jean", Personne.Sexe.HOMME, 20, 70),
new Personne("marie", Personne.Sexe.FEMME, 10, 30), new Personne("camille", Personne.Sexe.FEMME, 30, 55));
public static List<Personne> get() {
return personnes;
}
public static String toString(List<Personne> liste) {
try {
return new ObjectMapper().writeValueAsString(liste);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- السطور 10-11: قائمة تضم 3 أشخاص؛
- الأسطر 13-15: طريقة ثابتة للحصول على هذه القائمة؛
- الأسطر 17-23: طريقة ثابتة تسمح بالحصول على السلسلة jSON من قائمة الأشخاص التي تم تمريرها كمعلمة؛
سيتم استخدام هذه البيانات بواسطة الكود التالي:
package dvp.java8.lambdas;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple02 {
public static void main(String[] args) {
// المسند الذي تم تنفيذه بواسطة فئة مجهولة
Predicate<Personne> filterPoids=new Predicate<Personne>() {
@Override
public boolean test(Personne personne) {
return personne.getPoids()<50;
}
};
// المسند الذي يتم تنفيذه بواسطة لامدا
Predicate<Personne> filterAge = p -> p.getAge() < 28;
// قائمة بالأشخاص
List<Personne> personnes = Personnes.get();
// عدد مرات العرض
System.out.println(Personnes.toString(filterPersonnes(personnes, filterAge)));
System.out.println(Personnes.toString(filterPersonnes(personnes, filterPoids)));
}
private static List<Personne> filterPersonnes(List<Personne> personnes, Predicate<Personne> filter) {
// [filter] يقوم بتصفية القائمة [personnes]
List<Personne> personnesFiltrées = new ArrayList<>();
for (Personne p : personnes) {
if (filter.test(p)) {
personnesFiltrées.add(p);
}
}
return personnesFiltrées; }
}
- الأسطر 13-18: تنفيذ واجهة Predicate<Personne> بواسطة فئة مجهولة. يتعلق الأمر بفلتر على وزن الشخص؛
- السطر 20: تنفيذ واجهة Predicate<Personne> بواسطة دالة لامدا. وهو عبارة عن مرشح يعتمد على عمر الشخص. وبناءً على ما سبق، كان من الممكن كتابتها أيضًا بالطريقة التالية:
Predicate<Personne> filterAge = (Personne p) -> {
return (p.getAge() < 28);
};
لكن النسخة الموجودة في السطر 20 أكثر إيجازًا. يتم استنتاج نوع المعلمة p من السياق. نقوم هنا بإنشاء نوع [Predicate<Personne>]. وبذلك تكون توقيع الطريقة المُنفَّذة هي [boolean test(Personne param)]. وبالتالي، فإن النوع الضمني لـ p، في السطر 20، هو النوع [Personne]؛
- السطر 22: يتم استرداد قائمة بالأشخاص محددة مسبقًا؛
- السطر 24: يتم تصفية الأشخاص وفقًا لأعمارهم؛
- السطر 25: يتم تصفية الأشخاص وفقًا لوزنهم. في كلتا الحالتين، يتم عرض السلسلة jSON من القائمة التي تمت تصفيتها بهذه الطريقة؛
- الأسطر 28-37: طريقة ثابتة
- تقبل كمعلمات: قائمة بالأشخاص المراد تصفيتهم والمعيار. وهذا المعيار هو مثيل للواجهة [Predicate<Personne>]. ولأغراض التسهيل اللغوي، يُشار هنا وفي أماكن أخرى من الوثيقة إلى مثيل واجهة I بمثيل لفئة C التي تنفذ I؛
- تُرجع كنتيجة القائمة التي تم تصفيتها بهذه الطريقة؛
- السطر 32: يتم استخدام الطريقة [test] الخاصة بالواجهة [Predicate]. ووفقًا لمعيار التصفية الذي يتم تمريره إلى الطريقة، فإن الطريقة [test] ستكون:
return personne.getPoids()<50;
أو
return p.getAge() < 28
يؤدي تنفيذ الفئة [Exemple02] إلى النتيجة التالية:
4.3. المثال-03 - الواجهة الوظيفية Function<T,R>
![]() |
يتم تعريف الواجهة الوظيفية Function<T,R> على النحو التالي:

تحتوي الواجهة على طريقة واحدة فقط بتوقيع R apply(T t). تُستخدم هذه الطريقة عادةً لإنشاء نوع جديد Collection<R> انطلاقًا من نوع Collection<T>. لتوضيح هذه الواجهة، سنستخدم الكود التالي:
package dvp.java8.lambdas;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dvp.data.Personne;
import dvp.data.Personnes;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class Exemple03 {
public static void main(String[] args) throws JsonProcessingException {
// تنفيذ باستخدام فئة مجهولة
Function<Personne, String> mapToName = new Function<Personne, String>() {
@Override
public String apply(Personne personne) {
return personne.getNom();
}
};
// التنفيذ باستخدام لامدا
Function<Personne, Integer> mapToAge = p -> p.getAge();
// قائمة بالأشخاص
List<Personne> personnes = Personnes.get();
// jSON
ObjectMapper jsonMapper = new ObjectMapper();
// عمليات العرض
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
}
// تحويل List<Personne> إلى List<T>
private static <T> List<T> mapPersonnes(List<Personne> personnes, Function<Personne, T> mapper) {
List<T> maps = new ArrayList<>();
for (Personne p : personnes) {
maps.add(mapper.apply(p));
}
return maps;
}
}
- الأسطر 15-20: يتم تنفيذ الواجهة [Function<Personne, String>] باستخدام فئة مجهولة ستقوم بالتحويل من Personne إلى String؛
- السطر 22: يتم تنفيذ الواجهة [Function<Personne, Integer>] باستخدام دالة لامدا تقوم بالتحويل من Personne إلى Integer؛
- الأسطر 33-39: طريقة ثابتة
- تقبل معلمتين: الأولى من النوع List<Personne> وهي قائمة بالأشخاص المراد تحويلهم. والثانية من النوع Function<Personne, T> وهي دالة تقوم، انطلاقًا من كل شخص في القائمة، بإنشاء كائن من النوع T؛
- وتُرجع نتيجة من النوع List<T> حيث يأتي كل عنصر من التحويل Personne -> T؛
- الأسطر 35-37: يتم تطبيق التحويل Personne -> T. إذا كان المعلمة الثانية للطريقة هي الكائن [mapToName]، فسيتم إجراء تحويل Personne -> String. وإذا كان الكائن هو [mapToAge]، فسيتم إجراء تحويل «Personne» إلى «Integer»؛
والنتيجة التي تم الحصول عليها هي كما يلي:
4.4. مثال-04 - الواجهة الوظيفية Consumer<T>
![]() |
يتم تعريف الواجهة الوظيفية Consumer<T> على النحو التالي:

تتميز الطريقة الوحيدة في الواجهة بالتوقيع التالي: void accept(T t). تستخدم هذه الطريقة (تستهلك) معلمتها ولا تُرجع أي نتيجة. لتوضيح ذلك، سنستخدم الكود التالي:
package dvp.java8.lambdas;
import java.util.List;
import java.util.function.Consumer;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple04 {
public static void main(String[] args) {
// قائمة الأشخاص
List<Personne> personnes = Personnes.get();
// تنفيذ مجهول
Consumer<Personne> consumerAge = new Consumer<Personne>() {
@Override
public void accept(Personne personne) {
System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// تنفيذ لامدا
Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());
// عمليات العرض
for (Personne p : personnes) {
consumerAge.accept(p);
}
System.out.println("--------");
for (Personne p : personnes) {
consumerPoids.accept(p);
}
}
}
- الأسطر 14-19: يتم تنفيذ الواجهة [Consumer<Personne>] بواسطة فئة مجهولة تُعرض طريقة [accept] الخاصة بها اسم الشخص وعمره؛
- السطر 21: يتم تنفيذ الواجهة [Consumer<Personne>] بواسطة دالة لامدا، حيث تعرض الطريقة الضمنية [accept] اسم الشخص ووزنه؛
- الأسطر 23-25: يتم استخدام قائمة الأشخاص من خلال التنفيذ [consumerAge]؛
- الأسطر 27-29: يتم استخدام قائمة الأشخاص من قبل التنفيذ [consumerPoids]؛
والنتائج هي كما يلي:
4.5. مثال-05 - الواجهة الوظيفية BiConsumer<T,U>
![]() |
يتم تعريف الواجهة الوظيفية BiConsumer<T,U> على النحو التالي:

الطريقة الوحيدة للواجهة لها التوقيع التالي: void accept(T t, U u). وهي تستهلك النوع T مع المعلومات الإضافية U u. سنوضح استخدامها بالكود التالي:
package dvp.java8.lambdas;
import dvp.data.Personne;
import dvp.data.Personnes;
import java.util.List;
import java.util.function.BiConsumer;
public class Exemple05 {
public static void main(String[] args) {
// قائمة الأشخاص
List<Personne> personnes = Personnes.get();
// تنفيذ مجهول
BiConsumer<Personne, Integer> biconsumerAge = new BiConsumer<Personne, Integer>() {
@Override
public void accept(Personne personne, Integer integer) {
personne.setAge(personne.getAge() + integer);
System.out.printf("age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// تنفيذ لامدا
BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
p.setPoids(p.getPoids() + i);
System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
};
// عمليات العرض
for (Personne p : personnes) {
biconsumerAge.accept(p, 100);
}
System.out.println("--------");
for (Personne p : personnes) {
biconsumerPoids.accept(p, 200);
}
}
}
- الأسطر 14-20: تنفيذ الواجهة BiConsumer<T,U> باستخدام فئة مجهولة. تستخدم الطريقة [apply] معلمتها الثانية لتحديث عمر الشخص الذي تم تمريره كمعلمة أولى. ثم تعرض النتيجة؛
- الأسطر 22-25: تنفيذ الواجهة BiConsumer<T,U> باستخدام دالة لامدا. تستخدم الطريقة الضمنية [apply] المعلمة الثانية لتحديث وزن الشخص الذي تم تمريره في المعلمة الأولى. ثم تعرض النتيجة؛
- الأسطر 27-29: يتم استهلاك قائمة الأشخاص باستخدام التنفيذ [biconsumerAge]؛
- الأسطر 31-33: يتم استهلاك قائمة الأشخاص باستخدام التنفيذ [biconsumerPoids]؛
النتائج التي تم الحصول عليها هي كما يلي:
4.6. المثال-06 - الواجهة الوظيفية BiFunction<T,U,R>
![]() |
يتم تعريف الواجهة الوظيفية BiFunction<T,U,R> على النحو التالي:

الطريقة الوحيدة للواجهة لها التوقيع التالي: R apply(T t, U u). هذه الطريقة مشابهة للطريقة [BiConsumer.apply]، ولكن في حين أن الأخيرة لا تُرجع أي نتيجة، فإن الطريقة [BiFunction.apply] تُرجع نتيجة. سنوضح استخدامها من خلال الكود التالي:
package dvp.java8.lambdas;
import java.util.List;
import java.util.function.BiFunction;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple06 {
public static void main(String[] args) {
// قائمة الأشخاص
List<Personne> personnes = Personnes.get();
// تنفيذ مجهول
BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
@Override
public Integer apply(Personne personne, Integer integer) {
return personne.getAge() + integer;
}
};
// تنفيذ لامدا
BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
return p.getPoids() + i;
};
// عمليات العرض
for (Personne p : personnes) {
System.out.printf("age de %s = %s%n", p.getNom(), biFunctionAge.apply(p, 100));
}
System.out.println("--------");
for (Personne p : personnes) {
System.out.printf("poids de %s = %s%n", p.getNom(), biFunctionPoids.apply(p, 200));
}
}
}
- الأسطر 14-19: يتم تنفيذ الواجهة BiFunction<Personne, Integer, Integer> باستخدام فئة مجهولة. وتُرجع الطريقة [apply] التابعة لها عمر الشخص الذي تم تمريره كمعلمة أولى مضافًا إليه قيمة المعلمة الثانية؛
- الأسطر 21-23: يتم تنفيذ الواجهة BiFunction<Personne, Integer, Double> باستخدام دالة لامدا. تُرجع الطريقة [apply] وزن الشخص الذي تم تمريره في المعلمة الأولى مضافًا إليه قيمة المعلمة الثانية؛
- الأسطر 25-27: يتم تطبيق التنفيذ [biFunctionAge] على الأشخاص؛
- الأسطر 29-31: يتم تطبيق التنفيذ [biFunctionPoids] على الأشخاص؛
النتائج التي تم الحصول عليها هي كما يلي:
age de jean = 120
age de marie = 110
age de camille = 130
--------
poids de jean = 270.0
poids de marie = 230.0
poids de camille = 255.0
بالإضافة إلى دوال لامدا، أدخلت Java 8 النوع Stream<T> الذي يمثل تدفقًا لعناصر من النوع T. يمكن أن تخضع هذه العناصر لتحويلات متتالية يتم تنفيذها بواسطة دوال لامدا. عندما يكون ذلك ممكنًا، وفي حالة وجود عدة معالجات، يمكن أحيانًا إجراء هذه التحويلات بشكل متوازٍ.
4.7. المثال-07 - الواجهة الوظيفية Supplier<T>
![]() |
يتم تعريف الواجهة الوظيفية Supplier<T> على النحو التالي:

تتميز الطريقة الوحيدة في الواجهة بالتوقيع: T get()، وتتمثل مهمتها في توفير كائن من النوع T.
سنوضح هذه الواجهة الوظيفية باستخدام الكود التالي:
package dvp.java8.lambdas;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple07 {
public static void main(String[] args) {
// تنفيذ مجهول
Supplier<Personne> supplier = new Supplier<Personne>() {
// قائمة الأشخاص
List<Personne> personnes = Personnes.get();
// تنفيذ واجهة
@Override
public Personne get() {
int i = new Random().nextInt(personnes.size());
return personnes.get(i);
}
};
// التشغيل
for (int i = 0; i < 5; i++) {
affiche(supplier);
}
}
// عرض الشخص
public static void affiche(Supplier<Personne> supplier) {
System.out.println(supplier.get());
}
}
- الأسطر 13-28: تنفيذ نوع Supplier<Personne>؛
- الأسطر 31-33: تتوقع الطريقة الثابتة [affiche] معلمة من النوع Supplier<Personne>؛
- الأسطر 25-27: تشغيل المثيل Supplier<Personne>؛
ونحصل على النتائج التالية:






