ستشمل Javase8 التي تم إصدارها في عام 2013 خطة تسمى مشروع Lambda ، والتي تم وصفها في مسودة JSR-335 في يونيو من هذا العام.
قدم JSR-335 الإغلاق في Java. يوجد الإغلاق في العديد من اللغات الشائعة ، مثل C ++ ، C#. يتيح لنا الإغلاق إنشاء مؤشر وظيفة وتمريرها كمعلمة. في هذه المقالة ، سننظر تقريبًا إلى خصائص Java8 ونقدم تعبيرات Lambda. وسأحاول وضع بعض البرامج العينة لشرح بعض المفاهيم والقواعد.
توفر لنا لغة برمجة Java مفهوم الواجهة ، ويمكن تعريف الطريقة التجريدية في الواجهة. تحدد الواجهة واجهة برمجة التطبيقات وتأمل أن المستخدمين أو الموردين في تنفيذ هذه الأساليب. في كثير من الأحيان ، لا ننشئ فصول تنفيذ مستقلة لبعض الواجهات.
يستخدم استخدام مجهول على نطاق واسع. المشهد الأكثر شيوعًا المستخدم في الطبقة الداخلية المجهولة هو معالج الأحداث. ثانياً ، غالبًا ما يتم استخدام الفصول الداخلية المجهولة في البرامج المتعددة.
مثلما نناقش ، فئة مجهولة هي تنفيذ واجهة معينة من النينجا. عادةً ما نقوم بتمرير كائن فئة التنفيذ هذه كمعلمة بطريقة ما ، وبعد ذلك سوف تستدعي هذه الطريقة طريقة فئة التنفيذ إلى فئة التنفيذ داخليًا. لذلك ، تسمى هذه الواجهة واجهة رد الاتصال.
على الرغم من استخدام فئات مجهولة في كل مكان ، إلا أنها لا تزال تواجه العديد من المشاكل. المشكلة الرئيسية الأولى هي التعقيد. تجعل هذه الفئات مستويات الكود تبدو فوضوية ومعقدة ، والمعروفة أيضًا باسم الخصوصية الرأسية. ثانياً ، لا يمكنهم الوصول إلى الأعضاء غير النهائيين في فئة التغليف. ستصبح الكلمة الرئيسية لهذا المربكة للغاية. إذا كان لدى فئة مجهولة اسم العضو مثل فئة التغليف الخاصة بها ، فستكون المتغيرات الداخلية للمتغيرات الخارجية. لأن هذه الكلمة الرئيسية تستحق الكائن المجهول نفسه بدلاً من كائن التغليف الخاص به.
void public anonymousample () {String nonfinalVariable = "nonformal exmple" ؛ تشغيل المتغير "؛ // أسفل السطر يعطي خطأ التجميع. //system.out.println ("-> "" println ("->" + this.variable) ؛}}). الإخراج هو:
-> تشغيل طريقة التشغيل-> عضو فئة Runnable
يشرح هذا المثال المشكلة التي ذكرتها أعلاه ، ويحول تعبير Lambda تقريبًا جميع المشكلات الناجمة عن الطبقات الداخلية المجهولة. قبل أن نستكشف تعبير Lambda ، دعونا نلقي نظرة على الواجهات الوظيفية.
واجهات وظيفية
الواجهات الوظيفية هي واجهة ذات طريقة واحدة فقط ، والتي تمثل عقد الطريقة هذا.
واحد فقط في التعريف أعلاه ليس بهذه البساطة. لا أفهم هذه الفقرة.
يوضح المثال التالي بوضوح كيفية فهم مفهوم الواجهات الوظيفية.
واجهة Runnable {void run () ؛} // الوظيفية foo {boolean متساوية (Object OBJ) ؛} // غير وظيفية ؛ شريط الوظيفي ؛ ليس الوظيفي نفس التوقيعمعظم واجهات رد الاتصال هي واجهات وظيفية. على سبيل المثال ، Runnable ، قابلة للاتصال ، المقارنة ، إلخ. كان يطلق عليه سابقا سام (طريقة مجردة واحدة)
تعبير لامدا
كما قلنا ، هناك مشكلة رئيسية في فئة مجهولة هي أن مستوى الكود يبدو فوضويًا ، أي أن تعبير العمودي. تعبير Lambda يبدو وكأنه طريقة. لديهم قائمة معلمات رسمية وحظر هذه المعلمات.
(سلسلة S) -> S.Lengh ؛
يعني المثال أعلاه أن التعبير الأول يتلقى متغير سلسلة كمعلمة ، ثم يقوم بإرجاع طول السلسلة. الثاني بدون أي معلمات وإرجاع 43. أخيرًا ، قبل الثالث اثنين من عدد صحيح X و Y ، وعاد إلى السلام.
بعد قراءة الكثير من الكلمات ، أخيرًا ، يمكنني تقديم مثال على تعبير Lambda الأول.
الفئة العامة firstlambdaexpression {public string fariable = "متغير مستوى الفئة" ؛ سلسلة nonfinalvariable = "هذا هو متغير غير نهائي" ؛ ؛ الإخراج هو:
-> الطريقة المتغير المحلي-> متغير مستوى الفئة
يمكنك مقارنة الفرق بين استخدام تعبيرات Lambda والفئة الداخلية المجهولة. يمكننا أن نقول بوضوح أن كتابة فئات مجهولة باستخدام تعبير Lambda تحل مشكلة الرؤية المتغيرة. يمكنك إلقاء نظرة على التعليقات التوضيحية في الكود.
يتضمن بناء جملة تعبير Lambda العام قائمة المعلمات ، وهي الكلمة الرئيسية السهم "->" هي الهيئة الرئيسية. يمكن أن يكون الموضوع عبارة عن تعبير (بيان خط واحد) أو جملة متعددة الخطوط. إذا كان تعبيرًا ، فسيتم حسابه وإعادته. لا يمكن استخدام الكسر والمتابعة إلا داخل الدورة.
لماذا تختار هذا النموذج قواعد اللغة الخاصة ، لأن هذا النمط حاليًا عادة ما يكون في C# و Scala ، وهو أيضًا كتابة عامة لتعبير Lambda. هذا التصميم النحوي يحل أساسًا تعقيد النوع المجهول. ولكن في الوقت نفسه ، هو أيضًا مرن للغاية. نتيجة التعبير هي قيمة الإرجاع الخاصة به. هذه المرونة يمكن أن تبقي الكود بسيط.
يتم استخدام تعبير Lambda كمجهول ، بحيث يمكن استخدامها بمرونة في وحدات أخرى أو تعبيرات Lambda الأخرى (تعبيرات Lambda المتداخلة).
// تم إرفاق معرض Lambda مع BLOCK. (() -> {system.out.println ("تشغيل مؤشر ترابط مختلف") ؛}) ؛
إذا ألقيت نظرة فاحصة على تعبير Lambda ، فسترى أن نوع الواجهة الهدف ليس جزءًا من التعبير. المترجم يساعد على استنتاج النوع والبيئة المحيطة للتعبير Lambda.
يجب أن يكون تعبير Lambda نوعًا مستهدفًا ، ويمكن أن يتكيف مع أي نوع مستهدف محتمل. عندما يكون نوع الهدف واجهة ، يجب تلبية الشروط التالية لتجميعها بشكل صحيح:
نظرًا لأن المترجم يمكن أن يعرف نوع المعلمة والرقم من خلال عبارة نوع الهدف ، في تعبير Lambda ، يمكن حذف إعلان نوع المعلمة.
المقارنة C = (S1 ، S2) -> s1.comparetoignorecase (S2) ؛
علاوة على ذلك ، إذا كانت الطريقة المعلنة في النوع الهدف لا تتلقى سوى معلمة واحدة فقط (في كثير من الأحيان هذا هو الحال) ، فيمكن أيضًا كتابة الأقواس الصغيرة للمعلمة ، على سبيل المثال:
ActionListenr beadr = event-> event.getwhen () ؛
يأتي سؤال واضح للغاية ، لماذا لا يعبر Lambda عن اسم طريقة محدد؟
الجواب هو: تعبير Lambda لا يمكن استخدامه إلا للواجهة الوظيفية ، في حين أن الواجهة الوظيفية لها طريقة واحدة فقط.
عندما نحدد واجهة وظيفية لإنشاء تعبيرات lambda ، يمكن للمترجم أن يرى توقيع القانون الوظيفي الصيني والتحقق مما إذا كان التعبير المعطى يتطابق.
تساعدنا هذه القواعد النحوية المرنة على تجنب استخدام الخصوصية الرأسية المجهولة ، ولن يجلب الخصوصية الأفقية (جملة طويلة جدًا).
ترتبط قواعد التعبير Lambda بالسياق ، لكن هذه ليست المرة الأولى. لدى مشغلي الماس الذي أضيفته Java SE 7 هذا المفهوم أيضًا ، والذي يتم استنتاجه بواسطة السياق.
void invoke (runnable r) {R.Run ()} void future invoke (callable r) {return c.compute ()} // extres اثنين / ما الذي سيتم استدعاؤه سيتم استدعاؤه؟
الإجابة على السؤال أعلاه هي استدعاء طريقة تلقي المعلمة القابلة للاتصال. في هذه الحالة ، سيتم حل المترجم من خلال تحميل أنواع المعلمات المختلفة. عندما يكون هناك أكثر من طريقة تحميل قابلة للتطبيق ، يتحقق التحويل البرمجي أيضًا من توافق تعبير Lambda ونوع الهدف المقابل. ببساطة ، من المتوقع أن تعود طريقة الاستدعاء أعلاه ، ولكن طريقة استدعاء واحدة فقط لها قيمة إرجاع.
يمكن تحويل تعبيرات Lambda بشكل صريح إلى أنواع مستهدفة محددة ، طالما أنها متوافقة مع الأنواع المقابلة. بالنظر إلى البرنامج التالي ، قمت بتطبيق ثلاثة أنواع من القابلة للاتصال ، وقاموا جميعًا بتحويله إلى نوع فئة.
الفئة العامة firstwithlambdaexpressions {public static void main (string [] args) {listl = ArraysList (callial)-> "callable 1" ، (call able) ()-> "callable 2" ، (callable) (callable))-> "Call 3") ؛ ) {e1.printStackTrace () ؛} E.Shutdown () ؛} فوم الفراغ العام (قائمة) يلقي interuptexception ، executionExcepti على {for (المستقبل: قائمة) {system.out.println (future.get ()) ؛ }}}كما ناقشنا سابقًا ، لا يمكن للفئات المجهولة الوصول إلى المتغيرات غير الهامة في البيئة المحيطة. ولكن لا يوجد مثل هذا الحد في تعبير Lambda.
في الوقت الحاضر ، لا تنطبق الواجهات الوظيفية المحددة إلا على الواجهة. حاولت إنشاء تعبير Lambda عن طريقة مجردة واحدة فقط ، ولكن تم إجراء خطأ في التجميع. وفقًا لـ JSR -335 ، قد تدعم النسخة المستقبلية من تعبير Lambda الفئات الوظيفية.
اقتباس الطريقة
الطرق المشار إليها كطريقة مرجعية دون تسميتها.
يسمح لنا تعبير Lambda بتحديد طريقة مجهولة واستخدامها كمثيل للواجهة الوظيفية. تشبه الأساليب تعبير Lambda.
System :: GetProperty "ABC" :: PlayString :: FaeliverSuper :: ToStringArrayList :: New
يوضح البيان أعلاه بناء الجملة العام للطريقة والمرجع إلى المنشئ. لقد رأينا هنا شخصية تشغيل جديدة ":::: Double Colon). لا أعرف الاسم الدقيق مثل هذا المشغل ، لكن JSR يشير إليها على أنها فواصل ، وتشير صفحة ويكيبيديا إلى ذلك كعملية تحليل نطاق .
يتم وضع المرجع المستهدف أو المتلقي خلف المزود والفواصل. هذا يشكل تعبيرًا يمكن أن يقتبس طريقة. في البيان النهائي ، اسم الطريقة "جديد". يقتبس هذا التعبير طريقة بنية فئة ArrayList (الإشارة إلى المُنشئ في القسم التالي)
قبل أن تفهم هذا ، أود أن أسمح لك برؤية قوة الطريقة المقتبسة.
استيراد java.util.arrays = {موظف جديد ("نيك") ، موظف جديد ("روبن") ، موظف جديد ("جوش") ، موظف جديد ("آندي") ، موظف جديد ("مارك") ؛ قبل الفرز: ") ؛ dumpemployee (الموظفين) ؛ المصفوفات. aslist (الموظفين) {system.out.print (emp.name+"،") (Employeee EMP1 ، Employee EMP2) {return emp1.name.compareto (EMP2.Name) ؛}} الإخراج هو:
قبل الفرز: نيك ، روبن ، جوش ، آندي ، مارك ، بعد النوع: آندي ، جوش ، مارك ، نيك ، روبن ،
الإخراج ليس مميزًا. طريقة ثابتة MyCompare يتلقى كائنين الموظفين ويعيد أسمائهم للمقارنة.
في الطريقة الرئيسية ، قمت بإنشاء مجموعة مختلفة من الموظف ، وقمت بنقلها إلى المصفوفات.
انتظر لحظة ، إذا نظرنا إلى Javadoc ، فستجد أن المعلمة الثانية من طريقة الفرز هي نوع Corarator ، لكننا نمرر مرجع الطريقة الثابتة للموظف. المشكلة المهمة هنا.
دعونا نلقي نظرة على السبب. تتوقع طريقة sorrays.sort مثيلًا للمقارنة ، وهذا المقارنة عبارة عن واجهة وظيفية ، مما يعني أن لديه طريقة واحدة فقط ، أي مقارنة. نحن هنا أيضًا نمر بشكل ضار بتعبير Lambda ، والذي يوفر تنفيذ طريقة compaare في هذا التعبير. ولكن فينا ، لدى فئة الموظفين لدينا بالفعل طريقة مقارنة. فقط أن أسماءهم مختلفة.
عندما تكون هناك طرق متعددة ذات نفس الاسم ، سيختار المترجم أفضل مطابقة وفقًا لنوع الهدف. لفهم ، انظر إلى مثال:
int static int myCompare (Employeee EMP1 ، EMP2) {return emp1.name.comPareto (EMP2.Name) ؛} // طريقة أخرى بنفس الاسم. {{) {{) {{return int1.compareto (int2) ؛} لقد قمت بإنشاء صفيفتين مختلفتين للفرز.
الموظف [] الموظفين = {موظف جديد ("نيك") ، موظف جديد ("روبن") ، موظف جديد ("جوش") ، موظف جديد ("آندي") ، موظف جديد ("مارك") ؛ ins = {1 ، 4 ، 8 ، 2 ، 3 ، 8 ، 6} ؛ الآن ، أقوم بتنفيذ سطرين من الكود التاليين
Arrays.sort (الموظفين ، الموظف :: myCompare) ؛
هنا ، فإن طريقة المراجع في سطري الكود هي نفسها (الموظف :: MyCompare).
لا تضلل بالطريقة الثابتة ، يمكننا أيضًا إنشاء إشارة إلى طريقة المثال. بالنسبة للأساليب الثابتة ، نستخدم أسماء الفصول :: اسم طريقة الكتابة.
المثال أعلاه جيد جدًا ، لكن لا يتعين علينا كتابة طريقة لمقارنة عدد صحيح ، لأن Integer قد نفذت قابلة للمقارنة ويوفر مقارنة طريقة التنفيذ. لذلك نحن فقط نستخدم السطر التالي مباشرة:
Arrays.sort (ints ، integer :: compareto) ؛
رؤية هذا ، هل تشعر بالارتباك قليلاً؟ لا؟ ثم اسمحوا لي أن تخلطك هنا. تتم الإشارة إلى طريقة الأعضاء :: يجب أن يكون كائنًا من قبل ، ولكن لماذا تكون الجملة هنا شرعية بالفعل.
الجواب هو: يسمح هذا النوع من العبارات بالاستخدام في بعض الأنواع المحددة. Integer هو نوع بيانات ، وبالنسبة لنوع البيانات ، يُسمح بهذا البيان.
إذا قمنا بتحويل طريقة الموظف myCompare إلى غير ثابت ، ثم استخدمها: الموظف :: MyCompare ، ستكون هناك أخطاء في التجميع: لا توجد طريقة مناسبة.
مرجع الطريقة البناء
يتم استخدام مراجع المنشئ كصف يشير إلى مُنشئ بدون إضفاء الطابع المؤسسي المحدد.
مرجع الطريقة البناء هو ميزة جديدة من Javase 8. يمكننا إنشاء إشارة إلى طريقة بناءة ونمرها كمعلمة إلى نوع الهدف.
عندما نستخدمها للإشارة ، نقتبس طريقة موجودة واحدة لاستخدامها. وبالمثل ، عند استخدام مرجع الطريقة البناءة ، نقوم بإنشاء إشارة إلى الأساليب البناءة الحالية.
في القسم السابق ، رأينا اسم القواعد النحوي المشار إليه من قبل المنشئ :: الجديد ، والذي يبدو وكأنه مرجع الطريقة. يمكن تعيين الإشارة إلى هذه الطريقة التي تم إنشاؤها إلى مثيلات الواجهات الوظيفية المستهدفة. قد يكون هناك عدة مُنشئين في الفصل.
بالنسبة لي ، من الصعب كتابة الطريقة البناءة الأولى. في النهاية ، قضيت وقتًا طويلاً في العمل بجد ، وأخيراً "آه ، لقد وجدت ..." ، انظر إلى الإجراء التالي.
بنية الطبقة العامة {public static void main (string [] ar) {myInterface in = myClass :: new ؛ } الإخراج هو:
-> com.myclass@34e5307e
هذا يبدو مذهلاً بعض الشيء ، أليس كذلك؟
أثار هذا المثال مشكلة أخرى في قلبي: كيفية إنشاء طريقة بناءة مع معلمة؟ انظر إلى الإجراء أدناه:
CRASY CONSTRUCTORFERENCES {public static void main (ars [] ar) ؛ العمر) {this.name = name ؛ الإخراج هو:
-> اسم الموظف: جون-> عمر الموظف: 30
قبل قراءة هذه المقالة ، دعونا نلقي نظرة على أروع ميزة في طريقة Javase8-Default
الطرق الافتراضية
ستقدم Javase8 مفهومًا يسمى الطريقة الافتراضية. يحتوي إصدار Java المبكر من الواجهة على واجهة صارمة للغاية. في إصدار Java القادم ، يُسمح بالتنفيذ الافتراضي للطريقة في الواجهة. ليس الكثير من الهراء ، انظر إلى ما يلي:
الفئة العامة DefaultMethods {public static void (string [] ar) system.out.println ("-> myDefaultMethod") ؛}} class normalInterfaceImpl infermelemeRinterface {Override public void mynormalmethod () {) system.out.println ("-> myNormalmethod") ؛}}}} الإخراج هو:
-> MyDefaultMethod
تعلن الواجهة أعلاه طريقتين ، لكن فئة التنفيذ في هذه الواجهة لا تدرك سوى أحدهما ، لأن MyDefaultMethod يستخدم المعدلات الافتراضية ، ويوفر كتلة طريقة للتنفيذ الافتراضي. لا تزال قواعد التحميل الثقيلة في جنرال موتورز ساري المفعول هنا. إذا قام فئة التنفيذ بتطبيق الطريقة في الواجهة ، فستكون هذه الطريقة في فئة الاتصال عند الاتصال ، وإلا ، سيتم استدعاء التنفيذ الافتراضي.
يمكن أن تزيد واجهة الواجهة الأصل المتكاملة وتغييرها وإزالة التنفيذ الافتراضي للواجهة الأصل.
interface parentInterface {void inivalenomal () ؛ -> في البداية nomal ") ؛} void initiallydefault () ؛ // الآن طريقة طبيعية} في هذا المثال ، يحدد ParentInterface طريقتين ، ويتم تنفيذ الآخر بشكل افتراضي.
تخيل أن فئة ورثت الفئة C ، وأدركت أن الواجهة I ، وكان لدى C طريقة ، والطريقة التي وفرت الطريقة الافتراضية التي قدمت بها الطريقة الافتراضية متوافقة. في هذه الحالة ، ستعطي الطريقة في C الأولوية للطريقة الافتراضية في I ، وحتى الطريقة في C لا تزال أولوية عندما تكون الطريقة مجردة.
الفئة العامة defaultMethods {public static void main (string [] ar) ؛}} Interfaxe {public void defaultMethod () افتراضي {system.out.println ("-> interfaxe") ؛ الإخراج هو:
-> ParentClass
المثال الثاني هو أن صفي قد نفذت واجهتين مختلفتين ، ولكن كلا الواجهتين يوفران نفس البيان بنفس طريقة التنفيذ الافتراضية. في هذه الحالة ، لن يكون المترجم قادرًا على معرفة ما يجري. يمكن القيام بذلك بالطرق التالية.
الفئة العامة defaultMethods {public static void main (string [] ar) {firstInterface Impl = new NormalInterFampl () ؛ ) ؛}} الواجهة الثانية {public void defaultMethod () افتراضي الإخراج هو:
-> SecondInterface
الآن ، قرأنا مقدمة إغلاق جافا. في هذه المقالة ، تواصلنا مع الواجهات الوظيفية وإغلاق Java ، الذي فهم تعبير Java Lambda ، مراجع الطريقة ، ومراجع المنشئ. وقد كتبنا أيضًا مثالًا عالميًا على تعبير Lambda.
سيأتي Javase8 قريبًا.