English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
لأول مرة تعرفت على تعبير Lambda في TypeScript (مجال JavaScript)، وقد كان ذلك من أجل استخدام this في TypeScript خارج هذا الدليل وليس داخل هذا الدليل. بعد استخدامها، فكرت فجأة أن Lambda ليست ميزة جديدة ثقيلة في JDK8؟ لذا شعرت بقراءة المواد ذات الصلة وتسجيلها:
أ. السلوك المعياري
بالنسبة لمعيار السلوك يمكن قوله ببساطة أن جسم الدالة يحتوي فقط على كود عام للنمط بينما يتم نقل بعض المنطق الذي يتغير مع سيناريو العمل كمعامل إلى الدالة، يمكن للسلوك المعياري جعل البرنامج أكثر توافقًا مع الطلب المتغير بشكل متكرر.
بالنظر إلى سيناريو تجاري، لنفترض أننا بحاجة إلى تصفية تفاح من خلال البرنامج، دعنا أولاً نحدد كيان تفاح:
public class Apple { /** الرقم */ private long id; /** اللون */ private Color color; /** الوزن */ private float weight; /** موطن */ private String origin; public Apple() { } public Apple(long id, Color color, float weight, String origin) { this.id = id; this.color = color; this.weight = weight; this.origin = origin; } // تمرير getter وsetter }
المستخدم في البداية قد يكون الحاجة بسيطة فقط لتكون قادرًا على تصفية تفاح أخضر من خلال البرنامج، لذا يمكننا تنفيذ ذلك بسرعة من خلال البرنامج:
public static List<Apple> filterGreenApples(List<Apple> apples) { List<Apple> filterApples = new ArrayList<>(); for (final Apple apple : apples) { if (Color.GREEN.equals(apple.getColor())) { filterApples.add(apple); } } return filterApples; }
هذا الكود بسيط جدًا ولا يوجد ما يُذكر فيه. ولكن إذا أصبحت متطلبات المستخدم أخضرًا، يبدو تعديل الكود بسيطًا أيضًا، لا يزيد سوى عن تغيير الحكم على اللون من الأخضر إلى الأحمر. ولكن يجب علينا النظر في مشكلة أخرى، ماذا إذا تغيرت الشروط بشكل متكرر؟ إذا كان التغيير مجرد تغيير اللون، فذلك جيد، يمكننا تركيب شروط اللون مباشرة من قبل المستخدم، ومرور شروط الحكم كمعامل إلى دالة الحكم، ولكن ماذا إذا كان المستخدم لا يريد حكم اللون فقط، بل أيضًا الوزن وال حجم وما إلى ذلك؟ هل تعتقد أنك يمكن أن تضيف مختلف المعاملات للقيام بالحكم؟ ولكن هل هو الحل الصحيح بمرور المعاملات؟ إذا زادت شروط التصفية، أصبحت نموذج التجميع معقدًا، هل علينا النظر في جميع الحالات، وأن يكون لدينا استراتيجية لكل حالة؟ في هذه الحالة يمكننا تحويل السلوك إلى معامل، ونأخذ شروط التصفية كمعامل ونقوم بمرورها، في هذه الحالة يمكننا تعبئة واجهة الحكم:
public interface AppleFilter { */ * شرط التصفية الابستري * * @param apple * @return */ boolean accept(Apple apple); } */ * وضع شرط التصفية في واجهة * * @param apples * @param filter * @return */ public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) { List<Apple> filterApples = new ArrayList<>(); for (final Apple apple : apples) { if (filter.accept(apple)) { filterApples.add(apple); } } return filterApples; }
بعد التعبير عن السلوك بشكل عام، يمكننا تعيين شرط التصفية في مكان الاستدعاء المحدد، ونقوم بمرور الشروط كمعامل إلى الدالة، في هذه الحالة يتم استخدام طريقة الدالة الداخلية غير المسموح بها:
public static void main(String[] args) { List<Apple> apples = new ArrayList<>(); // فرز تفاح List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() { @Override public boolean accept(Apple apple) { // تنقيح التفاح الأحمر الذي يزيد وزنه عن 100g العودة Color.RED.equals(apple.getColor()) && apple.getWeight() > 100; } }); }
هذا التصميم يستخدم في الداخل أيضًا في JDK، مثل Java.util.Comparator، java.util.concurrent.Callable، عند استخدام هذه الواجهات، يمكننا تحديد تنفيذ الوظيفة المحددة في مكان التطبيق من خلال كائنات مجردة، ولكن من وجهة نظر الكود، على الرغم من أن هذا يبدو رائعًا، إلا أنه ليس مكتوبًا بشكل مختصر، في جافا 8 يمكننا تقليل هذا من خلال استخدام lambda:
// فرز تفاح List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100); //()->xxx حيث يتم استخدام () كمعاملات الوظيفة، xxx هي تنفيذ الوظيفة
ثانيًا: تعريف تعبيرات lambda
يمكننا تعريف تعبيرات lambda كأنها وظائف مجردة، قابلة للتنقل، حيث يجب أن نكون واضحين أولاً أن تعبيرات lambda هي في الأساس وظائف، على الرغم من أنها لا تنتمي إلى فئة معينة، إلا أنها تحتوي على قائمة معاملات، جسم الوظيفة، نوع العودة، وقابلة للإطلاق استثناءات؛ ثانيًا، إنها مجردة، لا تحتوي على اسم وظيفة محدد؛ يمكن نقل تعبيرات lambda مثل المعاملات، مما يقلل بشكل كبير من كتابة الكود. وتبدو تعريفها كما يلي:
نموذج أولي: قائمة المعاملات -> تعبير
نموذج ثاني: قائمة المعاملات -> {مجموعة تعبيرات}
من الضروري ملاحظة أن تعبيرات lambda تحتوي على كلمة العودة بشكل تلقائي، لذا لا نحتاج إلى كتابة كلمة العودة بشكل صريح في تعبيرات واحدة فقط، ولكن عندما تكون تعبيرات عدة، فإننا بحاجة إلى إضافة كلمة العودة بشكل صريح، ونستخدم الأسهم الكاملة { } لتغليف عدة تعبيرات، والآن لنرى بعض الأمثلة:
// العودة طول الكلمة المقدمة، العودة غير صريحة (String s) -> s.length() // طريقة لا تحتوي على أي معاملات، العودة دائمًا 42 () -> 42 // إذا كانت تعبيرات متعددة، يتم استخدام الأسهم الكاملة (int x, int y) -> { int z = x * y; العودة x + z; }
ثالثاً: استخدام تعبيرات lambda مع واجهة الوظيفية
استخدام تعبيرات lambda يتطلب استعانة بواجهة الوظيفية، مما يعني أن يمكننا استخدامه في مواضع وجود واجهة وظيفية فقط، لتحسين تعبيرات lambda.
واجهة الوظيفية المخصصة:
تعريف واجهة الوظيفية بأنها واجهة تحتوي على طريقة واحدة فقط فقط من الطرق التجريبية. تحسين جافا 8 في تعريف الواجهة هو إدخال الطريقة المسبقة، مما يتيح لنا تقديم تنفيذ افتراضي للمتغيرات في واجهة، ولكن مهما كان عدد الطرق المسبقة الموجودة، إذا كانت هناك طريقة واحدة فقط من الطرق التجريبية، فإنها تعد واجهة وظيفية، مثلما هو موضح في (الاستشهاد بالAppleFilter السابق):
*/ * واجهة تصفية التفاح */ @FunctionalInterface public interface AppleFilter { */ * شرط التصفية الابستري * * @param apple * @return */ boolean accept(Apple apple); }
يحتوي AppleFilter على طريقة واحدة فقط abstract method accept(Apple apple)، يمكن اعتبارها واجهة وظيفية، حيث أضفنا علامة @FunctionalInterface في تعريفها لتمييزها كواجهة وظيفية، ولكن هذه العلامة إختيارية، عند إضافة هذه العلامة، تقوم محرر الكود بتعيين هذه الواجهة لتكون لها طريقة واحدة فقط، وإلا سيتم إصدار خطأ، لذا يُنصح بإضافة هذه العلامة إلى واجهات الوظيفية.
واجهات الوظائف المدمجة في JDK:
تم دمج واجهات الوظائف المدمجة في JDK لاستخدام تعبيرات lambda، وسيتم شرح أمثلة على استخدام Predicate<T>، Consumer<T>، Function<T, R).
Predicate:
@FunctionalInterface public interface Predicate<T> { */ * يقييم هذا predicate على المعامل المقدم. * /* @param t المعامل المدخل */ * @return {@code true} إذا كان المعامل المدخل يتطابق مع predicate, * في غير ذلك {@code false} */ boolean test(T t); }
وظيفة Predicate تشبه AppleFilter أعلاه، حيث تستخدم الشروط الم设定的 في الخارج للتحقق من المعلمات المدخلة وتقوم بإرجاع النتيجة boolean، ويتم استخدام Predicate لفرز عناصر مجموعة List:
*/ * /* @param list */ * @param predicate /* @param <T> */ * @return */ public <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> newList = new ArrayList<T>(); for (final T t : list) { إذا (predicate.test(t)) { newList.add(t); } } return newList; }
استخدام:
demo.filter(list, (String str) -> null != str && !str.isEmpty());
Consumer
@FunctionalInterface public interface Consumer<T> { */ /* يتم تنفيذ هذه العملية على المعامل المقدم. */ * /* @param t المعامل المدخل */ */ void accept(T t); }
Consumer يقدم دالة استقبال متميزة، وتقبل المعامل دون إرجاع قيمة، والآن يتم استخدام Consumer لمرور الكolekta.
*/ /* مرور الكolekta وتنفيذ سلوك مخصص */ * /* @param list */ /* @param consumer */ /* @param <T> */ */ public <T> void filter(List<T> list, Consumer<T> consumer) { for (final T t : list) { consumer.accept(t); } }
باستخدام واجهة الفونكشن المقدمة أعلاه، يتم مرور الكolekta من النصوص وتحديد النصوص الفارغة.
demo.filter(list, (String str) -> { if (StringUtils.isNotBlank(str)) { System.out.println(str); } });
Function
@FunctionalInterface public interface Function<T, R> { */ * يطبق هذه الدالة على المعامل المقدم. * /* @param t الدالة على المعامل */ /* @return النتيجة الخاصة بالوظيفة */ */ R apply(T t); }
Funcation تنفيذ عملية التحويل، المدخل هو بيانات النوع T، والناتج هو بيانات النوع R، والآن يتم استخدام Function للتحويل الكolekta.
public <T, R> List<R> filter(List<T> list, Function<T, R> function) { List<R> newList = new ArrayList<R>(); for (final T t : list) { newList.add(function.apply(t)); } return newList; }
آخر:
demo.filter(list, (String str) -> Integer.parseInt(str));
تقدم هذه الواجهات الوظيفية بعض التحققات الافتراضية للعمليات، سيتم الحديث عنها لاحقًا عند تقديم الطرق الافتراضية في واجهات Java 8 ~
بعض الأمور التي يجب مراعاتها أثناء الاستخدام:
تقدير النوع
في عملية البرمجة، قد يشكك بعض الأحيان في ما إذا كان الكود سيقوم بتطابق أي واجهة وظيفية معينة، في الواقع سيقوم المترجم بتقييمها بشكل صحيح بناءً على المعاملات، نوع العودة، نوع الاستثناءات (إذا كان موجودًا)
في النداء الفعلي، في بعض الأحيان يمكننا تجاهل نوع المعامل لتعقيد أقل للكود:
// فرز تفاح List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100); // في بعض الحالات يمكننا تجاهل نوع المعامل، سيقوم المترجم بتقييم النوع بشكل صحيح بناءً على السياق List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.R ED.equals(apple.getColor()) && apple.getWeight() >= 100);
متغير محلي
في جميع الأمثلة السابقة، استخدمنا lambda تعبيرات باستخدام متغيراتها الرئيسية، يمكننا أيضًا استخدام متغيرات محلية في lambda، مثل
int weight = 100; List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);
في هذا المثال، نستخدم متغير weight المحلي في lambda، ولكن يجب أن تكون اللاحقات المحلية في lambda معلنة بشكل صريح أو في الواقع final، هذا يرجع إلى أن اللاحقات المحلية تُخزن في الرفيق، بينما تُشغل تعبير lambda في نواة أخرى، عندما يحاول هذا النواة الوصول إلى المتغير المحلي، هناك احتمال أن يتم تغيير أو إزالة المتغير، لذا لا يوجد مشكلة أمنية عند استخدام final.
أربعة. الإستدلال على الطريقة
يمكن استخدام الاستدلال على الطريقة لتعقيد أقل للكود، في بعض الأحيان يجعل هذا التبسيط الكود يبدو أكثر وضوحًا، لنرى مثالًا:
/* ... يتم تجاهل عملية إعداد apples */ // تستخدم تعبير lambda apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight())); // يتم استخدام الإشارة إلى الطريقة apples.sort(Comparator.comparing(Apple::getWeight));
الإشارات إلى الطريقة تعتمد على :: لربط الطريقة المثبتة بالطريقة نفسها، وتقسم إلى ثلاثة أنواع رئيسية:
الطريقة الثابتة
(args) -> ClassName.staticMethod(args)
تحويل إلى
ClassName::staticMethod
الطريقة المثبتة للمعامل
(args) -> args.instanceMethod()
تحويل إلى
ClassName::instanceMethod // ClassName هو نوع args
الطريقة المثبتة الخارجية
(args) -> ext.instanceMethod(args)
تحويل إلى
ext::instanceMethod(args)
المراجع:
http://www.codeceo.com/article/lambda-of-java-8.html
ما تم ذكره أعلاه هو جمالية JDK8 الجديدة Lambda، آمل أن تكون مفيدًا لك، إذا كان لديك أي أسئلة، فلا تتردد في ترك تعليق، وسأقوم بالرد على أسئلتك في أقرب وقت ممكن. وأشكركم أيضًا على دعمكم لموقع呐喊 التعليمي!
البيان: محتويات هذا المقال تم جمعها من الإنترنت، حقوق الطبع والنشر مملوكة للمالك الأصلي، تم إضافة المحتوى من قبل مستخدمي الإنترنت بطرقهم الخاصة، هذا الموقع لا يملك حقوق الملكية، لم يتم تعديل المحتوى بشكل يدوي، ولا يتحمل أي مسؤولية قانونية متعلقة بذلك. إذا كنت قد وجدت محتوى يشتبه في انتهاك حقوق الطبع والنشر، فلا تتردد في إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (أثناء إرسال البريد الإلكتروني، يرجى استبدال #بـ @) لتقديم الشكوى، وتقديم الدليل ذات الصلة، إذا تم التحقق من ذلك، سيتم حذف المحتوى المزعوم فورًا.