وجهة نظري حول تعبيرات Lambda في Java متشابكة تمامًا:
واحد أعتقد بهذه الطريقة: تعبيرات Lambda تقلل من تجربة القراءة لبرامج Java. لم تكن برامج Java رائعة في التعبير. على العكس من ذلك ، فإن أحد العوامل التي تجعل جافا شعبية هي الأمن والمحافظة-حتى المبتدئين يمكن أن يكتبوا رمزًا قويًا وسهل الولادة طالما أنهم ينتبهون إليه. تعبيرات Lambda لها متطلبات أعلى نسبيًا للمطورين ، لذلك تزيد أيضًا من بعض صعوبات الصيانة.
شيء آخر أعتقد أنه: كرمز رمز ، من الضروري تعلم وقبول ميزات جديدة للغة. إذا تخليت عن نقاط قوته التعبيرية لمجرد تجربة القراءة السيئة ، فإن بعض الناس يجدون صعوبة في فهم حتى تعبيرات trinocular. تتطور اللغة أيضًا ، وسيتم ترك أولئك الذين لا يستطيعون مواكبة ذلك طواعية.
لا أريد أن أترك وراءه. ومع ذلك ، إذا اضطررت إلى الاختيار ، فإن قراري كان لا يزال متحفظًا نسبيًا: ليست هناك حاجة لاستخدام Lambda في لغة Java - فهو يجعل الكثير من الناس في دائرة Java الحالية غير معتادة عليها ، وسيؤدي إلى زيادة في تكاليف العمالة. إذا كنت تحب ذلك كثيرًا ، فيمكنك التفكير في استخدام Scala.
على أي حال ، ما زلت بدأت أحاول إتقان Lambda ، بعد كل شيء ، تستخدم بعض التعليمات البرمجية التي يتم الاحتفاظ بها في العمل Lambda (ثق بي ، سأقوم بإزالته تدريجياً). البرامج التعليمية التي يجب تعلمها هي برامج تعليمية ذات صلة على موقع Oracle Java الرسمي.
― ― ― ― ― ― ― ― ― ― ― ―
لنفترض أنه يتم إنشاء تطبيق الشبكة الاجتماعية حاليًا. تتمثل إحدى الميزات في أنه يمكن للمسؤولين تنفيذ بعض الإجراءات على الأعضاء الذين يستوفون المعايير المحددة ، مثل إرسال الرسائل. يصف الجدول التالي حالة الاستخدام هذه بالتفصيل:
| مجال | يصف |
| اسم | الإجراءات اللازمة لأداء |
| المشاركون الرئيسيين | المسؤول |
| المتطلبات الأساسية | تسجيل دخول المسؤول إلى النظام |
| ما بعد الشروط | فقط إجراء الإجراءات للأعضاء الذين يستوفون المعايير المحددة |
| سيناريو النجاح الرئيسي | 1. يقوم المسؤول بتعيين معايير تصفية الأعضاء المستهدفين لأداء العملية ؛ 2. المسؤول يختار الإجراء لأداء ؛ 3. ينقر المسؤول على زر إرسال ؛ 4. يجد النظام الأعضاء الذين يستوفون المعايير المحددة ؛ 5. يقوم النظام بإجراء عمليات محددة مسبقًا على الأعضاء الذين يستوفون المعايير المحددة. |
| ممتد | قبل تحديد عملية التنفيذ أو قبل النقر فوق الزر "إرسال" ، يمكن للمسؤول اختيار ما إذا كان سيتم معاينة معلومات الأعضاء التي تلبي معايير التصفية. |
| تواتر الحدوث | يحدث عدة مرات في اليوم. |
استخدم فئة الشخص التالية لتمثيل معلومات الأعضاء في الشبكات الاجتماعية:
شخص الطبقة العامة {public enum sex {ذكر ، أنثى} اسم سلسلة ؛ عيد ميلاد محلي ؛ جنس الجنس سلسلة البريد الإلكتروني. public int getage () {// ...} public printperson () {// ...}}افترض أنه يتم حفظ جميع الأعضاء في قائمة <Person>.
في هذا القسم ، نبدأ بطريقة بسيطة للغاية ، ثم نحاول تنفيذها باستخدام الفصول المحلية والفصول المجهولة ، وفي النهاية سنختبر تدريجيًا قوة وفعالية تعبيرات Lambda. يمكن العثور على الرمز الكامل هنا.
الحل 1: إنشاء طرق للعثور على الأعضاء الذين يستوفون المعايير المحددة الأولى تلو الأخرى
هذا هو الحل البسيط والأقسوة لتنفيذ الحالات المذكورة أعلاه: إنه إنشاء عدة طرق وكل طريقة تتحقق من معيار (مثل العمر أو الجنس). يتحقق الرمز التالي من أن العمر أقدم من قيمة محددة:
printpersonsolderthan الفراغ العام الثابت (قائمة <Person> القائمة ، int age) {for (person p: rother) {if (p.getage ()> = age) {p.printperson () ؛ }}}هذا حل هش للغاية ، ومن المحتمل جدًا ألا يتم تشغيل التطبيق بسبب القليل من التحديث. إذا أضفنا متغيرات الأعضاء الجديدة إلى فئة الشخص أو تغيير الخوارزمية لقياس العمر في المعيار ، فنحن بحاجة إلى إعادة كتابة الكثير من التعليمات البرمجية للتكيف مع هذا التغيير. علاوة على ذلك ، فإن القيود هنا صلبة للغاية. على سبيل المثال ، ماذا يجب أن نفعل إذا أردنا طباعة أعضاء أصغر من قيمة محددة؟ إضافة طريقة جديدة أخرى printpersonsyourgerthan؟ من الواضح أن هذه طريقة غبية.
الحل 2: إنشاء طريقة أكثر عمومية
الطريقة التالية أكثر قابلية للتكيف من printpersonsolderthan. هذه الطريقة تطبع معلومات الأعضاء داخل الفئة العمرية المحددة:
printpersonspersonswithInagerange (قائمة <Person> ، int low ، int High) {for (person p: rother) {if (low <= p.getage () && p.getage () <high) {p.printperson () ؛ }}}الآن هناك فكرة جديدة: ماذا يجب أن نفعل إذا أردنا طباعة معلومات الأعضاء عن الجنس المحدد ، أو التي تلبي الجنس المحدد ودخل الفئة العمرية المحددة؟ ماذا لو قمنا بضبط فئة الشخص وأضفوا خصائص مثل الصداقة والموقع الجغرافي. على الرغم من أن أساليب الكتابة مثل هذا أكثر عالمية من PrintPersonsyourghanthan ، فإن كتابة طريقة لكل استعلام ممكن يمكن أن تؤدي أيضًا إلى هش في الكود. من الأفضل وضع رمز الفحص القياسي في فئة جديدة.
الحل 3: تنفيذ الفحص القياسي في فئة محلية
الطريقة التالية تطبع معلومات الأعضاء التي تلبي معايير البحث:
printpersons public static void (قائمة <Person> قائمة ، checkperson tester) {for (person p: rother) {if (tester.test (p)) {p.printperson () ؛ }}}يتم استخدام اختبار كائن CheckPerso في البرنامج للتحقق من كل مثيل في قائمة المعلمة القائمة. إذا كانت Tester.test () تُرجع صحيحًا ، فسيتم تنفيذ طريقة printperson (). من أجل تعيين معايير البحث ، يجب تنفيذ واجهة CheckPerson.
الفئة التالية تنفذ CheckPerson وتوفر تنفيذًا محددًا لطريقة الاختبار. تقوم طريقة الاختبار في هذه الفئة بتصفية معلومات عن العضوية التي تلبي متطلبات الخدمة العسكرية في الولايات المتحدة: أي جنس الذكور والعمر بين 18 و 25 عامًا.
checkPersonAivelForSelectiveservice تنفذ checkperson {اختبار منطقي عام (person p) {return p.gender == person.sex.male && p.getage ()> = 18 && p.getage () <= 25 ؛ }}لاستخدام هذه الفئة ، تحتاج إلى إنشاء مثيل وتشغيل طريقة PrintPersons:
printpersons (قائمة ، checkPersonAbelForSelectEviveservice ()) ؛
يبدو الرمز الآن أقل هشاشة - لا نحتاج إلى إعادة كتابة الكود بسبب التغييرات في بنية فئة الشخص. ومع ذلك ، لا يزال هناك رمز إضافي هنا: واجهة محددة حديثًا تحدد فئة داخلية لكل معيار بحث في التطبيق.
نظرًا لأن CheckPerson -Checkperson -Cheverselectiveservice تنفذ واجهة ، يمكن استخدام فئة مجهولة الهوية دون تحديد فئة داخلية لكل معيار.
الحل 4: استخدم فئات مجهولة لتنفيذ الفحص القياسي
معلمة واحدة في طريقة printpersons التي تسمى أدناه هي فئة مجهولة. إن وظيفة هذه الفئة المجهولة هي نفس وظيفة فئة CheckPersonableForSeviveService في المخطط 3: جميعهم أعضاء مرشحين بنوع الجنس وأعمار تتراوح أعمارهم بين 18 و 25 عامًا.
PrintPersons (قائمة ، checkperson () {public boolean test (person p) {return p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25 ؛}}) ؛يقلل هذا المخطط من كمية الترميز ، لأنه لم تعد هناك حاجة لإنشاء فئات جديدة لكل مخطط بحث. ومع ذلك ، لا يزال من غير المريح بعض الشيء القيام بذلك: على الرغم من أن واجهة CheckPerson لديها طريقة واحدة فقط ، إلا أن الفئة المجهولة التي تم تنفيذها لا تزال مطوّلة بعض الشيء وضخمة. في هذا الوقت ، يمكنك استخدام تعبير Lambda لاستبدال الفصول المجهولة. سيشرح ما يلي كيفية استخدام تعبير Lambda لاستبدال الفئات المجهولة.
الحل 5: استخدم تعبيرات Lambda لتنفيذ فحص قياسي
واجهة CheckPerson هي واجهة وظيفية. تشير الواجهة الوظيفية المزعومة إلى أي واجهة تحتوي فقط على طريقة مجردة واحدة. (يمكن أن تحتوي الواجهة الوظيفية أيضًا على طرق افتراضية متعددة أو طرق ثابتة). نظرًا لوجود طريقة مجردة واحدة فقط في الواجهة الوظيفية ، يمكن حذف اسم الطريقة للطريقة عند تنفيذ طريقة هذه الواجهة الوظيفية. لتنفيذ هذه الفكرة ، يمكنك استبدال تعبيرات فئة مجهولة مع تعبيرات Lambda. في طريقة PrintPersons التي تم إعادة كتابتها أدناه ، يتم تمييز الكود ذي الصلة:
printpersons (قائمة ، (person p) -> p.getGender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25) ؛
يمكنك أيضًا استخدام واجهة وظيفة قياسية لاستبدال واجهة CheckPerson ، وبالتالي تبسيط الرمز.
الحل 6: استخدم واجهات وظيفية قياسية في تعبيرات Lambda
لنلقي نظرة على واجهة CheckPerson:
واجهة checkperson {boolean test (person p) ؛ }هذه واجهة بسيطة للغاية. نظرًا لوجود طريقة مجردة واحدة فقط ، فهي أيضًا واجهة وظيفية. هذه الطريقة التجريدية تقبل معلمة واحدة فقط وتُرجع قيمة منطقية. هذه الواجهة التجريدية بسيطة للغاية لدرجة أننا سننظر فيما إذا كان من الضروري تحديد مثل هذه الواجهة في التطبيق. في هذا الوقت ، يمكنك التفكير في استخدام واجهات وظيفية قياسية تحددها JDK ، ويمكنك العثور على هذه الواجهات تحت حزمة java.util.function.
في هذا المثال ، يمكننا استخدام واجهة <T> المسند لاستبدال CheckPerson. هناك طريقة اختبار منطقي (T T) في هذه الواجهة:
واجهة المسند <T> {اختبار منطقي (T T) ؛ }واجهة <T> المسند هي واجهة عامة. تحدد فئة عامة (أو واجهة عامة) معلمات نوع أو أكثر باستخدام زوج من أقواس الزاوية (<>). هناك معلمة نوع واحد فقط في هذه الواجهة. عندما تعلن أو تثبيت فئة عامة باستخدام فئة ملموسة ، تحصل على فئة معلمة. على سبيل المثال ، فإن مسند الفئة المعلمة <Phone> مثل هذا:
interface protect <profer> {boolean test (person t) ؛ }في هذه الفئة المعلمة ، هناك طريقة تتفق مع المعلمات وقيم الإرجاع لطريقة اختبار checkperson.boolean (person p). لذلك ، يمكنك استخدام واجهة <T> المسند لاستبدال واجهة CheckPerson كما هو موضح في الطريقة التالية:
printpersonspersonswithswithswithswith (قائمة <Person> ، المسند <Person> tester) {for (person p: rother) {if (tester.test (p)) {p.printperson () ؛ }}}ثم استخدم الكود التالي لتصفية أعضاء الخدمة العسكرية كما في الخطة 3:
printpersonswithpredicate (قائمة ، p -> p.getGender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25) ؛
هل لاحظت أنه عند استخدام Predicate <Phone> كنوع المعلمة ، لا يتم تحديد نوع معلمة صريح. ليس هذا هو المكان الوحيد الذي يتم فيه تطبيق تعبيرات Lambda. سوف يقدم المخطط التالي المزيد من استخدام تعبيرات Lambda.
الحل 7: استخدم تعبيرات Lambda في جميع أنحاء التطبيق
دعونا نلقي نظرة على طريقة printpersonswith -predpredicate ونفكر فيما إذا كان يمكنك استخدام تعبيرات lambda هنا:
printpersonspersonswithswithswithswith (قائمة <Person> ، المسند <Person> tester) {for (person p: rother) {if (tester.test (p)) {p.printperson () ؛ }}}في هذه الطريقة ، يتم فحص كل مثيل شخص في القائمة باستخدام اختبار مثيل المسند. إذا كان مثيل الشخص يتوافق مع معايير الشيكات المحددة في المختبر ، فسيتم تشغيل طريقة PrintPerson لمثيل الشخص.
بالإضافة إلى تشغيل طريقة PrintPerson ، يمكن لحالات الشخص التي تلبي معيار الاختبار تنفيذ طرق أخرى. يمكنك التفكير في استخدام تعبير Lambda لتحديد الطريقة التي سيتم تنفيذها (أعتقد أن هذه الميزة جيدة ، والتي تحل المشكلة التي لا يمكن تمرير الأساليب في Java ككائنات). أنت الآن بحاجة إلى تعبير Lambda مشابهًا لطريقة PrintPerson - تعبير Lambda الذي يتطلب فقط معلمة واحدة وإرجاع void. تذكر شيئًا واحدًا: لاستخدام تعبيرات Lambda ، تحتاج إلى تنفيذ واجهة وظيفية أولاً. في هذا المثال ، هناك حاجة إلى واجهة وظيفية ، والتي تحتوي على طريقة مجردة واحدة فقط. هذه الطريقة التجريدية لها معلمة من نوع الشخص والعودة إلى الفراغ. يمكنك إلقاء نظرة على مستهلك الواجهة الوظيفية القياسية <T> التي توفرها JDK ، والتي لديها طريقة تجريدية باطلة (T T) تلبي هذا المطلب فقط. في الكود التالي ، استخدم مثيل المستهلك <T> لاستدعاء طريقة القبول بدلاً من p.printperson ():
Public Static Void ProcessPersons (قائمة <Person> قائمة ، مسند <Person> اختبار ، المستهلك <Person> block) {for (person p: rother) {if (tester.test (p)) {block.accept (p) ؛ }}}في المقابل ، يمكنك استخدام الكود التالي لتصفية أعضاء عصر الخدمة العسكرية:
ProcessPersons (قائمة ، p -> p.getGender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25 ، p -> p.printperson ()) ؛
إذا كنا نريد القيام بأشياء لا تُطبع فقط معلومات الأعضاء ، ولكن المزيد من الأشياء ، مثل التحقق من العضوية ، والحصول على معلومات الاتصال الأعضاء ، وما إلى ذلك. في هذه المرحلة ، نحتاج إلى واجهة وظيفية مع طريقة قيمة الإرجاع. وظيفة الواجهة الوظيفية القياسية لـ JDK <T ، R> لها طريقة مثل هذه R تنطبق (T T). تحصل الطريقة التالية على بيانات من Mapper المعلمة وتنفيذ السلوك المحدد بواسطة كتلة المعلمة على هذه البيانات:
Public Static Void ProcessPersOnsWithFunction (قائمة <Person> قائمة ، مسند <Person> اختبار ، وظيفة <شخص ، سلسلة> mapper ، المستهلك <string> block) {for (person p: rother) {if (tester.test (p)) {String data = mapper.apply (p) ؛ block.accept (data) ؛ }}}يحصل الرمز التالي على معلومات البريد الإلكتروني لجميع أعضاء عصر الخدمة العسكرية في قائمة وطباعةها:
ProcessPersOnSwithFunction (قائمة ، p -> p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25 ، p -> p.getemailaddress () ، البريد الإلكتروني -> system.out.println (البريد الإلكتروني)) ؛
الحل 8: استخدم الأدوية الجيرية بشكل متكرر أكثر
دعنا نراجع طريقة ProcessPersonswithFunction. فيما يلي نسخة عامة من هذه الطريقة. تتطلب الطريقة الجديدة المزيد من التسامح في أنواع المعلمات:
static static <x ، y> void prosistelements (iterable <x> source ، protect <x> tester ، function <x ، y> mapper ، consumer <y> block) {for (x p: source) {if (tester.test (p)) {y data = mapper.apply (p) ؛ block.accept (data) ؛ }}}لطباعة معلومات الأعضاء للخدمة العسكرية في العصر المناسب ، يمكنك استدعاء طريقة المعالجات مثل ما يلي:
المعالجات (قائمة ، p -> p.getGender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25 ، p -> p.getemailaddress () ، البريد الإلكتروني -> system.out.println (البريد الإلكتروني)) ؛
أثناء عملية استدعاء الطريقة ، يتم تنفيذ السلوك التالي:
الحصول على معلومات الكائن من مجموعة ، في هذا المثال ، الحصول على معلومات كائن الشخص من قائمة مثيل المجموعة.
تصفية الكائنات التي يمكن أن تطابق اختبار مثيل المسند. في هذا المثال ، فإن الكائن المسند هو تعبير lambda يحدد شروط تصفية الخدمة العسكرية في العصر المناسب.
يتم تسليم الكائن المصفى إلى خريطة كائن دالة للمعالجة ، وسيطابق الخريطة قيمة مع هذا الكائن. في هذا المثال ، يكون Mapper كائن الوظيفة عبارة عن تعبير Lambda يعيد عنوان البريد الإلكتروني لكل عضو.
يحدد السلوك من قبل كتلة كائن المستهلك للقيمة التي تطابقها Mapper. في هذا المثال ، فإن كائن المستهلك هو تعبير Lambda ، وهو وظيفة طباعة السلسلة ، وهو عنوان البريد الإلكتروني العضو الذي تم إرجاعه بواسطة Mapper مثيل الوظيفة.
الحل 9: استخدم عملية التجميع باستخدام تعبير Lambda كمعلمة
يستخدم الرمز التالي عملية التجميع لطباعة عناوين البريد الإلكتروني لأعضاء العصر العسكري في مجموعة القائمة:
ritter.stream () .filter (p -> p.getgender () == person.sex.male && p.getage ()> = 18 && p.getage () <= 25) .map (p -> p.getemailaddress ()).
تحليل عملية تنفيذ الكود أعلاه وتنظيم الجدول التالي:
سلوك | عملية التجميع |
احصل على الكائن | دفق <e> دفق () |
كائنات تصفية التي تتطابق مع المعايير المحددة للمثيل المسند | دفق <T> مرشح (مسند <؟ super t> التنبؤ) |
احصل على قيمة المطابقة للكائن من خلال مثيل الوظيفة | <r> دفق <r> خريطة (وظيفة <؟ super t ،؟ تمتد r> mapper) |
تنفيذ السلوك المحدد بواسطة مثيل المستهلك | void foreach (المستهلك <؟ super t> action) |
العمليات المرشح والخريطة والفورش في الجدول كلها عمليات إجمالية. تأتي العناصر التي تتم معالجتها بواسطة عملية التجميع من الدفق ، وليس مباشرة من المجموعة (أي ، لأن الطريقة الأولى التي تسمى في برنامج المثال هي التيار ()). الدفق هو تسلسل البيانات. على عكس المجموعات ، لا يقوم Stream بتخزين البيانات بهيكل معين. بدلاً من ذلك ، يحصل Stream على بيانات من مصدر معين ، مثل مجموعة ، من خلال خط أنابيب. خط الأنابيب عبارة عن تسلسل تشغيل للتيار ، في هذا المثال Filter-Map-Foreach. بالإضافة إلى ذلك ، عادة ما تستخدم عمليات التجميع تعبيرات Lambda كمعلمات ، مما يمنحنا أيضًا مساحة مخصصة.