لماذا تستخدم تعبيرات لامدا
دعونا نلقي نظرة على بعض الأمثلة:
المثال الأول هو تنفيذ مهمة في مؤشر ترابط منفصل ، عادة ما ننفذه على النحو التالي:
يقوم عامل الفئة بتنفيذ Runnable {public void run () {for (int i = 0 ؛ i <100 ؛ i ++) dowork () ؛ } ...} عامل w = عامل جديد () ؛ موضوع جديد (w) .start () ؛المثال الثاني هو طريقة مقارنة السلسلة المخصصة (حسب طول السلسلة) ، والتي تتم عمومًا:
Class LenightComparator يطرف المقارنة <string> {public int compare (السلسلة الأولى ، السلسلة الثانية) {return integer.compare (first.length () ، second.length ()) ؛ }} arrays.sort (سلاسل ، طول جديد مندوبور ()) ؛في المثال الثالث ، في Javafx ، أضف رد اتصال إلى زر:
button.setOnAction (New EventHandler <ActionEvent> () {public void Handle (ActionEvent event) {system.out.println ("شكرًا على النقر!") ؛}}) ؛تحتوي هذه الأمثلة على شيء واحد مشترك ، وهو أنها تحدد أولاً كتلة من التعليمات البرمجية ، أو تمريرها إلى كائن أو طريقة ، ثم تنفيذها. قبل تعبيرات Lambda ، لا تسمح Java بالتمرير المباشر للكتل الكود ، لأن Java موجهة نحو الكائن ، لذلك يجب تمرير كائن لتغليف كتلة التعليمات البرمجية المراد تنفيذها في الكائن.
بناء جملة التعبير Lambda
يتم التعبير عن LengthComparator في المثال الثاني أعلاه كتعبير Lambda:
(سلسلة أولاً ، السلسلة الثانية) -> integer.compare (first.length () ، second.length ()) ؛
-> قبل هو قائمة المعلمات ، تليها هيئة بيان التعبير ؛
إذا كان جسم بيان التعبير أكثر من سطر واحد ، فسيتم كتابة جسم البيان في {} ، تمامًا مثل الوظيفة العادية:
(سلسلة أولاً ، السلسلة الثانية) -> {if (first.length ()> second.length ()) {return 1 ؛ } آخر if (first.length () == second.length ()) {return 0 ؛ } آخر {return -1 ؛ }} ؛إذا لم تكن هناك معلمات ، () لا تزال بحاجة إلى إحضار معك. على سبيل المثال ، يمكن التعبير عن المثال الأول أعلاه على النحو التالي:
() -> {for (int i = 0 ؛ i <1000 ؛ i ++) {dowork () ؛ }}إذا كان يمكن استنتاج نوع المعلمة تلقائيًا من السياق ، فيمكنك حذف:
المقارنة <string> comp = (أولاً ، ثانية) // نفس (السلسلة الأولى ، السلسلة الثانية) -> integer.compare (first.length () ، second.length ()) ؛
إذا كانت هناك معلمة واحدة فقط ويمكن استنتاج النوع تلقائيًا ، فيمكن أيضًا حذف الأقواس ():
// بدلاً من (الحدث) -> أو (حدث ActionEvent) -> EventHandler <ActionEvent> المستمع = event -> system.out.println ("شكرًا على النقر!") ؛يتم استنتاج نوع قيمة الإرجاع لتعبير Lambda تلقائيًا ، لذلك لا يلزم تحديده ؛ في تعبير Lambda ، فإن بعض الفروع الشرطية لها قيم إرجاع ، لكن الفروع الأخرى لا تحتوي على قيم إرجاع ، وهو أمر غير مسموح به ، مثل:
(x) -> {if (x> = 0) {return 1 ؛ }}بالإضافة إلى ذلك ، فإن الفرق بين تعبير Lambda و Trature Lambda هو أن التعبير Lambda لا يحتاج إلى كتابة الكلمة الرئيسية للعودة. سيعود وقت تشغيل Java نتيجة التعبير كقيمة الإرجاع ، في حين أن العبارة Lambda هي تعبير مكتوب في {} ، ويجب استخدام الكلمة الرئيسية للعودة ، على سبيل المثال:
// التعبير lambdacomparator <string> comp1 = (الأول ، الثاني) -> integer.compare (first.length () ، second.length ()) ؛ // بيان lambdacomparator <string> comp2 = (الأول ، الثاني) -> {return integer.compare (first.length () ، second.length ()) ؛}} ؛ واجهة وظيفية
إذا كانت الواجهة تحتوي على طريقة مجردة واحدة فقط ، فسيتم تسميتها
الواجهة الوظيفية ، مثل Runnable ، المقارنة ، إلخ.
في أي مكان يلزم وجود كائن الواجهة الوظيفية ، يمكنك استخدام تعبيرات Lambda:
arrays.sort (الكلمات ، (الأول ، الثاني) -> integer.compare (first.length () ، second.length ())) ؛
هنا ، تتطلب المعلمة الثانية من sort () كائن المقارنة ، والمقارن هو
واجهة وظيفية ، حتى تتمكن من المرور مباشرة في تعبير Lambda. عند استدعاء طريقة المقارنة () للكائن ، فإنه هو تنفيذ هيئة البيان في تعبير Lambda ؛
إذا كان بيان تعبير Lambda يلقي استثناءً ، فيجب أن ترمي الطريقة التجريدية المقابلة في الواجهة الوظيفية الاستثناء ، وإلا فمن الضروري التقاط الاستثناء بشكل صريح في تعبير Lambda:
RunNable r = ()-> {system.out.println ("-------") ؛ حاول {thread.sleep (10) ؛ } catch (interruptedException e) {// catch issection}} ؛ callable <string> c = ()-> {system.out.println ("----------") ؛ thread.sleep (10) ؛ يعود ""؛}؛ طريقة المرجع
إذا تم تمرير معلمات تعبير Lambda كمعلمات إلى طريقة ما وتأثير تنفيذها هو نفسه ، يمكن التعبير عن تعبير Lambda باستخدام مرجع الطريقة ، والطريقتين التاليتين متكافئتين:
(x) -> system.out.println (x) system.out :: println
من بينها ، system.out :: println يسمى المرجع الطريقة.
مرجع الطريقة يأتي بشكل أساسي في ثلاثة أشكال:
بالنسبة للطريقتين الأولين ، فإن معلمات تعبير Lambda المقابلة ومعلمات الطريقة هي نفسها ، مثل:
system.out :: println (x) -> system.out.println (x) Math :: Pow (x ، y) -> Math.Pow (x ، y)
بالنسبة للطريقة الثالثة ، في نص عبارة تعبير Lambda المقابل ، يتم استخدام المعلمة الأولى ككائن ، يتم استدعاء الطريقة ، ويتم استخدام المعلمات الأخرى كمعلمات الطريقة ، مثل:
String :: ComparetoignoreCase (S1 ، S2) -> S1.comparetoignorecase (S2) 1.5 مرجع مُنشئ
يشبه مرجع المنشئ مرجع الطريقة ، لكنه طريقة خاصة: جديدة. يتم تحديد المنشئ المحدد من خلال بيئة السياق ، مثل:
قائمة <String> absels = ... ؛ دفق <button> dream = labels.stream (). الخريطة (الزر :: جديد) ؛
الزر :: الجديد يعادل (x) -> زر (x) ، وبالتالي فإن المُنشئ المسمى هو: button (x) ؛
بالإضافة إلى إنشاء كائن واحد ، يمكنك أيضًا إنشاء مجموعة من الكائنات ، مثل المكافئين التاليين:
int [] :: new (x) -> new int [x]
نطاق متغير
تعبيرات Lambd التقاط المتغيرات المتاحة في النطاق الحالي ، مثل:
public void repertmessage (نص سلسلة ، عدد int) {runnable r = () -> {for (int i = 0 ؛ i <count ؛ i ++) {system.out.println (text) ؛ thread.yield () ؛ }} ؛ موضوع جديد (ص) .start () ؛}ولكن هذه المتغيرات يجب أن تكون غير قابلة للتغيير ، لماذا؟ انظر المثال التالي:
int تطابق = 0 ؛ لـ (path p: files) new thread (() -> {if (p يحتوي على بعض الخصائص) matches ++ ؛}). start () ؛ // غير قانوني لتحور المبارياتنظرًا لأن المتغيرات القابلة للتغيير ليست آمنة مؤشرات ترابط في تعبيرات Lambda ، فإن هذا يتماشى مع متطلبات الفئات الداخلية ، ويمكن الرجوع فقط إلى المتغيرات النهائية المحددة من الخارج في الفئات الداخلية ؛
إن نطاق تعبير Lambda هو نفسه كتلة الكود المتداخل ، وبالتالي لا يمكن أن يتعارض اسم المعلمة أو الاسم المتغير في تعبير Lambd مع المتغيرات المحلية ، مثل:
path first = paths.get ("/usr/bin") ؛ المقارن <string> comp = (الأول ، الثاني) -> integer.compare (first.length () ، second.length ()) ؛ // خطأ: متغير أولاً محدد بالفعلإذا تمت الإشارة إلى هذا المتغير في تعبير Lambda ، فإن المرجع هو هذا المتغير للطريقة التي تنشئ تعبير Lambda ، مثل:
تطبيق الفئة العامة () {public void dowork () {runNable Runner = () -> {... ؛ System.out.println (this.toString ()) ؛ ...} ؛ }} لذا ، فإن هذا.
أشياء.
الطريقة الافتراضية
لا يمكن أن يكون هناك سوى طرق مجردة في الواجهة. إذا تمت إضافة طريقة جديدة إلى واجهة موجودة ، فإن جميع فئات التنفيذ في الواجهة تحتاج إلى تنفيذ هذه الطريقة.
يقدم Java 8 مفهوم الطريقة الافتراضية ، ويضيف طريقة افتراضية إلى الواجهة ، والتي لن تدمر قواعد الواجهة الحالية. يمكن لفئة تنفيذ الواجهة اختيار تجاوز أو ورث الطريقة الافتراضية مباشرة ، مثل:
واجهة الشخص {long getId () ؛ السلسلة الافتراضية getName () {return "John Q. Public" ؛ }}Java يسمح وراثة متعددة. كيفية التعامل مع هذا الصراع إذا كانت الأساليب المحددة في الفئة الأم للفئة هي بالضبط نفس الأساليب الافتراضية المحددة في الواجهة ، أو الواجهتان من الفصل هي نفسها تمامًا ، كيف تتعامل مع هذا الصراع؟ قواعد المعالجة هي كما يلي:
إذا كانت الطريقة تتعارض بين فئة الأصل والواجهة: يجب أن تسود الأساليب في الفئة الأصل ، ويجب تجاهل الأساليب الموجودة في الواجهة ؛
إذا تعارضت الطريقة الافتراضية في الواجهتين ، فأنت بحاجة إلى تجاوز الطريقة لحل الصراع ؛
طريقة ثابتة
قبل Java 8 ، يمكن تعريف المتغيرات الثابتة فقط في الواجهة. بدءًا من Java 8 ، يمكن إضافة طرق ثابتة إلى الواجهة ، مثل
أضافت واجهة المقارنة سلسلة من الطرق الثابتة للمقارنة xxx ، مثل:
static public <T> المقارنة <T> المقارنة (tointfunction <؟ super t> keyextractor) {objects.requirenonnull (keyextractor) ؛ العودة (المقارنة <T> و Serializable) (C1 ، C2) -> Integer.compare (keyextractor.applyasint (c1) ، keyextractor.applyasint (c2)) ؛}باستخدام هذه الطريقة الثابتة ، فإن الطريقتين التاليتين متكافئة أيضًا:
1.
Arrays.sort (Cities ، (First ، Second) -> Integer.compare (first.length () ، second.length ())) ؛
2.
Arrays.sort (Cities ، Comparator.comParingInt (String :: length)) ؛
لذلك ، عندما نقوم بتصميم واجهاتنا الخاصة في المستقبل ، لم نعد بحاجة إلى تحديد فئات الأدوات المنفصلة (مثل المجموعات/التجميع).
فقط استخدم الطريقة الثابتة في الواجهة.
فئة داخلية مجهولة
في عالم Java ، يمكن للفصول الداخلية المجهولة تنفيذ عمليات لا يمكن تنفيذها إلا مرة واحدة في التطبيق. على سبيل المثال ، في تطبيق Android ، يتم معالجة حدث النقر فوق الزر. لا تحتاج إلى كتابة فئة منفصلة للتعامل مع حدث نقرة ، يمكنك القيام بذلك مع فئة داخلية مجهولة:
زر زر = (زر) findViewById (مثال Lambda 1. Runnable Lambda دعونا نلقي نظرة على عدة أمثلة. فيما يلي مثال على Runnable:
public void runnableTest () {system.out.println ("=== RunNableTest ===") ؛ // a anonymous Runnable R1 = new RunNable () {Override public void run () {system.out.println ("Hello World One!") ؛ }} ؛ // lambda runnable r2 = () -> system.out.println ("Hello World Two!") ؛ // تنفيذ وظيفتين تشغيل r1.run () ؛ r2.run () ؛ } public void runnableTest () {system.out.println ("=== RunNableTest ===") ؛ // a anonymous Runnable R1 = new RunNable () {Override public void run () {system.out.println ("Hello World One!") ؛ }} ؛ // lambda runnable r2 = () -> system.out.println ("Hello World Two!") ؛ // تنفيذ وظيفتين تشغيل r1.run () ؛ r2.run () ؛ } لا يتم إرجاع التنفيذ ولا قيمة الإرجاع. تعبيرات Lambda Runable تستخدم كتل التعليمات البرمجية لتبسيط رمز العناصر المكونة من خمسة عناصر في عبارة واحدة. الفئة العامة شخص {سلسلة خاصة بعدم لقب السلسلة الخاصة ؛ عصر INT الخاص ؛ الجنس بين الجنسين ؛ البريد الإلكتروني الخاص بالسلسلة الخاصة ؛ سلسلة سلسلة خاصة ؛ عنوان السلسلة الخاصة ؛} الفئة العامة شخص {سلسلة خاصة بعدم لقب السلسلة الخاصة ؛ عصر INT الخاص ؛ الجنس بين الجنسين ؛ البريد الإلكتروني الخاص بالسلسلة الخاصة ؛ سلسلة سلسلة خاصة ؛ عنوان السلسلة الخاصة ؛} فيما يلي كيفية تنفيذ واجهة المقارنة باستخدام فئات داخلية مجهولة الهوية وتعبيرات Lambda: Comparatorest classortest {public static void main (string [] args) {list <Person> personlist = person.createShortList () ؛ // استخدم الفئة الداخلية لتنفيذ مجموعة الفرز. System.out.println ("=== sorted ASC surname ===") ؛ لـ (person p: personlist) {p.printName () ؛ }. collections.sort (personlist ، (person p1 ، person p2) -> p1.getSurname (). compareto (p2.getSurname ())) ؛ لـ (person p: personlist) {p.printName () ؛ } // desc system.out.println desc collections.sort (personlist ، (p1 ، p2) -> p2.getSurname (). compareto (p1.getSurname ())) ؛ لـ (person p: personlist) {p.printName () ؛ }}} Comparatorest classortest {public static void main (string [] args) {list <Person> personlist = person.createShortList () ؛ // استخدم الفئة الداخلية لتنفيذ مجموعة الفرز. System.out.println ("=== sorted ASC surname ===") ؛ لـ (person p: personlist) {p.printName () ؛ }. collections.sort (personlist ، (person p1 ، person p2) -> p1.getSurname (). compareto (p2.getSurname ())) ؛ لـ (person p: personlist) {p.printName () ؛ } // desc system.out.println desc collections.sort (personlist ، (p1 ، p2) -> p2.getSurname (). compareto (p1.getSurname ())) ؛ لـ (person p: personlist) {p.printName () ؛ }}} يمكنك أن ترى أنه يمكن تنفيذ فصول داخلية مجهولة من خلال تعبيرات Lambda. لاحظ أن تعبير Lambda الأول يحدد نوع المعلمة كشخص ؛ تعبير Lambda الثاني يغفل تعريف النوع. تعبيرات Lambda دعم ضربة قاضية نوع ، وإذا كان يمكن استنتاج النوع المطلوب من خلال السياق ، يمكن حذف تعريف النوع. هنا ، نظرًا لأننا نستخدم تعبيرات Lambda في مقارن يستخدم تعريفات عامة ، يمكن للمترجم أن يستنتج هاتين المعلمتين كشخص.