CodeGym /جاوا بلاگ /Random-UR /جاوا میں لیمبڈا اظہار کی وضاحت۔ مثالوں اور کاموں کے ساتھ۔...
John Squirrels
سطح
San Francisco

جاوا میں لیمبڈا اظہار کی وضاحت۔ مثالوں اور کاموں کے ساتھ۔ حصہ 1

گروپ میں شائع ہوا۔
یہ مضمون کس کے لیے ہے؟
  • یہ ان لوگوں کے لیے ہے جو سوچتے ہیں کہ وہ جاوا کور کو پہلے ہی اچھی طرح جانتے ہیں، لیکن جاوا میں لیمبڈا اظہار کے بارے میں کوئی اشارہ نہیں ہے۔ یا ہوسکتا ہے کہ انہوں نے لیمبڈا کے تاثرات کے بارے میں کچھ سنا ہو، لیکن تفصیلات کی کمی ہے۔
  • یہ ان لوگوں کے لیے ہے جو لیمبڈا کے تاثرات کی ایک خاص سمجھ رکھتے ہیں، لیکن پھر بھی ان سے پریشان ہیں اور انہیں استعمال کرنے کے عادی نہیں ہیں۔
جاوا میں لیمبڈا اظہار کی وضاحت۔  مثالوں اور کاموں کے ساتھ۔  حصہ 1 - 1اگر آپ ان میں سے کسی ایک زمرے میں فٹ نہیں ہیں، تو ہو سکتا ہے کہ آپ کو یہ مضمون بورنگ، ناقص یا عام طور پر آپ کی چائے کا کپ نہ لگے۔ اس معاملے میں، بلا جھجھک دوسری چیزوں کی طرف بڑھیں یا، اگر آپ کو اس موضوع پر عبور حاصل ہے، تو براہ کرم تبصروں میں تجاویز دیں کہ میں مضمون کو کس طرح بہتر یا اضافی بنا سکتا ہوں۔ مواد کی کوئی علمی قدر ہونے کا دعویٰ نہیں ہے، نیاپن کو چھوڑ دیں۔ بالکل اس کے برعکس: میں ان چیزوں کو بیان کرنے کی کوشش کروں گا جو پیچیدہ ہیں (کچھ لوگوں کے لیے) جتنا ممکن ہو۔ اسٹریم API کی وضاحت کرنے کی درخواست نے مجھے یہ لکھنے کی ترغیب دی۔ میں نے اس کے بارے میں سوچا اور فیصلہ کیا کہ میری ندی کی کچھ مثالیں لیمبڈا کے تاثرات کو سمجھے بغیر ناقابل فہم ہوں گی۔ تو ہم لیمبڈا اظہار کے ساتھ شروع کریں گے۔ اس مضمون کو سمجھنے کے لیے آپ کو کیا جاننے کی ضرورت ہے؟
  1. آپ کو آبجیکٹ پر مبنی پروگرامنگ (OOP) کو سمجھنا چاہیے، یعنی:

    • طبقات، اشیاء، اور ان کے درمیان فرق؛
    • انٹرفیس، وہ کلاسوں سے کیسے مختلف ہیں، اور انٹرفیس اور کلاسز کے درمیان تعلق؛
    • طریقے، انہیں کیسے پکارا جائے، تجریدی طریقے (یعنی بغیر نفاذ کے طریقے)، طریقہ کار کے پیرامیٹرز، طریقہ کار کے دلائل اور انہیں کیسے پاس کیا جائے۔
    • رسائی میں ترمیم کرنے والے، جامد طریقے/متغیر، حتمی طریقے/متغیر؛
    • کلاسز اور انٹرفیس کی وراثت، انٹرفیس کی متعدد وراثت۔
  2. جاوا کور کا علم: عام قسمیں (جنرک)، مجموعے (فہرستیں)، تھریڈز۔
ٹھیک ہے، آئیے اس کی طرف آتے ہیں۔

ایک چھوٹی سی تاریخ

لیمبڈا کے تاثرات جاوا میں فنکشنل پروگرامنگ سے آئے، اور وہاں ریاضی سے۔ ریاستہائے متحدہ میں 20 ویں صدی کے وسط میں، الونزو چرچ، جو ریاضی اور تمام قسم کے تجریدات کا بہت شوقین تھا، پرنسٹن یونیورسٹی میں کام کرتا تھا۔ یہ الونزو چرچ تھا جس نے لیمبڈا کیلکولس کی ایجاد کی، جو ابتدائی طور پر تجریدی خیالات کا ایک مجموعہ تھا جو مکمل طور پر پروگرامنگ سے متعلق نہیں تھا۔ ایلن ٹورنگ اور جان وون نیومن جیسے ریاضی دان ایک ہی وقت میں پرنسٹن یونیورسٹی میں کام کرتے تھے۔ سب کچھ ایک ساتھ آیا: چرچ لیمبڈا کیلکولس کے ساتھ آیا۔ ٹیورنگ نے اپنی تجریدی کمپیوٹنگ مشین تیار کی، جسے اب "Turing مشین" کے نام سے جانا جاتا ہے۔ اور وون نیومن نے ایک کمپیوٹر فن تعمیر کی تجویز پیش کی جس نے جدید کمپیوٹرز کی بنیاد بنائی ہے (جسے اب "وان نیومن فن تعمیر" کہا جاتا ہے)۔ اس وقت، الونزو چرچ کے خیالات اس کے ساتھیوں کے کاموں کے طور پر اتنے مشہور نہیں تھے (خالص ریاضی کے شعبے کو چھوڑ کر)۔ تاہم، تھوڑی دیر بعد جان میک کارتھی (جو کہ پرنسٹن یونیورسٹی سے فارغ التحصیل بھی تھے اور ہماری کہانی کے وقت میساچوسٹس انسٹی ٹیوٹ آف ٹیکنالوجی کا ملازم تھا) چرچ کے خیالات میں دلچسپی لینے لگے۔ 1958 میں، اس نے ان خیالات کی بنیاد پر پہلی فعال پروگرامنگ زبان، LISP بنائی۔ اور 58 سال بعد، فنکشنل پروگرامنگ کے آئیڈیاز جاوا 8 میں لیک ہو گئے۔ ابھی 70 سال بھی نہیں گزرے... سچ کہوں، ریاضی کے خیال کو عملی طور پر لاگو کرنے کے لیے یہ سب سے طویل عرصہ نہیں ہے۔

معاملے کا دل

لیمبڈا اظہار ایک قسم کا فنکشن ہے۔ آپ اسے جاوا کا ایک عام طریقہ سمجھ سکتے ہیں لیکن دلیل کے طور پر دوسرے طریقوں کو منتقل کرنے کی مخصوص صلاحیت کے ساتھ۔ یہ ٹھیک ہے. نہ صرف نمبروں، تاروں اور بلیوں کو طریقوں سے منتقل کرنا ممکن ہو گیا ہے بلکہ دوسرے طریقے بھی! ہمیں اس کی کب ضرورت ہو سکتی ہے؟ یہ مددگار ثابت ہوگا، مثال کے طور پر، اگر ہم کچھ کال بیک طریقہ پاس کرنا چاہتے ہیں۔ یعنی، اگر ہمیں اس طریقہ کی ضرورت ہو جس کو ہم کہتے ہیں کہ کسی دوسرے طریقے کو کال کرنے کی اہلیت ہو جسے ہم اس تک پہنچاتے ہیں۔ دوسرے لفظوں میں، اس لیے ہمارے پاس مخصوص حالات میں ایک کال بیک اور دوسروں میں ایک مختلف کال بیک پاس کرنے کی صلاحیت ہے۔ اور اس طرح ہمارا طریقہ جو ہمارے کال بیکس وصول کرتا ہے انہیں کال کرتا ہے۔ ترتیب دینا ایک سادہ سی مثال ہے۔ فرض کریں کہ ہم کچھ ہوشیار ترتیب دینے والا الگورتھم لکھ رہے ہیں جو اس طرح لگتا ہے:

public void mySuperSort() { 
    // We do something here 
    if(compare(obj1, obj2) > 0) 
    // And then we do something here 
}
بیان میں if، ہم اس compare()طریقہ کو کہتے ہیں، موازنہ کرنے کے لیے دو اشیاء میں سے گزرنا، اور ہم جاننا چاہتے ہیں کہ ان میں سے کون سی چیز "زیادہ" ہے۔ ہم فرض کرتے ہیں کہ "بڑا" "کم" سے پہلے آتا ہے۔ میں اقتباسات میں "زیادہ سے بڑا" ڈالتا ہوں، کیونکہ ہم ایک ایسا عالمگیر طریقہ لکھ رہے ہیں جو نہ صرف صعودی ترتیب میں بلکہ نزولی ترتیب میں بھی ترتیب دینے کا طریقہ جان سکے گا (اس صورت میں، "بڑی" چیز درحقیقت "کم" چیز ہو گی۔ ، اور اس کے برعکس)۔ اپنی ترتیب کے لیے مخصوص الگورتھم سیٹ کرنے کے لیے، ہمیں اسے اپنے mySuperSort()طریقہ کار میں منتقل کرنے کے لیے کچھ طریقہ کار کی ضرورت ہے۔ اس طرح ہم اپنے طریقہ کو "کنٹرول" کرنے کے قابل ہو جائیں گے جب اسے بلایا جائے گا۔ یقیناً، ہم دو الگ الگ طریقے لکھ سکتے ہیں — mySuperSortAscend()اور mySuperSortDescend()— صعودی اور نزولی ترتیب میں ترتیب دینے کے لیے۔ یا ہم طریقہ پر کچھ دلیل دے سکتے ہیں (مثال کے طور پر، بولین متغیر؛ اگر صحیح ہے، تو صعودی ترتیب میں ترتیب دیں، اور اگر غلط، تو نزولی ترتیب میں)۔ لیکن کیا ہوگا اگر ہم کسی پیچیدہ چیز کو ترتیب دینا چاہتے ہیں جیسے سٹرنگ اری کی فہرست؟ ہمارے mySuperSort()طریقہ کار کو کیسے معلوم ہوگا کہ ان سٹرنگ اریوں کو کیسے ترتیب دیا جائے؟ سائز سے؟ تمام الفاظ کی مجموعی لمبائی سے؟ شاید حروف تہجی کی بنیاد پر صف میں پہلی تار کی بنیاد پر؟ اور کیا ہوگا اگر ہمیں کچھ معاملات میں صفوں کی فہرست کو صف کے سائز کے لحاظ سے ترتیب دینے کی ضرورت ہے، اور دوسرے معاملات میں ہر صف کے تمام الفاظ کی مجموعی لمبائی کے مطابق؟ میں توقع کرتا ہوں کہ آپ نے موازنہ کرنے والوں کے بارے میں پہلے ہی سنا ہو گا اور یہ کہ اس صورت میں ہم صرف اپنے چھانٹنے کے طریقہ پر ایک موازنہ آبجیکٹ بھیجیں گے جو مطلوبہ ترتیب دینے والے الگورتھم کو بیان کرتا ہے۔ کیونکہ معیاری sort()طریقہ اسی اصول کی بنیاد پر لاگو کیا جاتا ہے mySuperSort()، میں sort()اپنی مثالوں میں استعمال کروں گا۔

String[] array1 = {"Dota", "GTA5", "Halo"}; 
String[] array2 = {"I", "really", "love", "Java"}; 
String[] array3 = {"if", "then", "else"}; 

List<String[]> arrays = new ArrayList<>(); 
arrays.add(array1); 
arrays.add(array2); 
arrays.add(array3); 

Comparator<;String[]> sortByLength = new Comparator<String[]>() { 
    @Override 
    public int compare(String[] o1, String[] o2) { 
        return o1.length - o2.length; 
    } 
}; 

Comparator<String[]> sortByCumulativeWordLength = new Comparator<String[]>() { 

    @Override 
    public int compare(String[] o1, String[] o2) { 
        int length1 = 0; 
        int length2 = 0; 
        for (String s : o1) { 
            length1 += s.length(); 
        } 

        for (String s : o2) { 
            length2 += s.length(); 
        } 

        return length1 - length2; 
    } 
};

arrays.sort(sortByLength);
نتیجہ:
  1. Dota GTA5 Halo
  2. if then else
  3. I really love Java
یہاں صفوں کو ہر صف میں الفاظ کی تعداد کے حساب سے ترتیب دیا گیا ہے۔ کم الفاظ والی صف کو "کم" سمجھا جاتا ہے۔ اس لیے پہلے آتا ہے۔ زیادہ الفاظ والی صف کو "زیادہ سے بڑا" سمجھا جاتا ہے اور اسے آخر میں رکھا جاتا ہے۔ اگر ہم طریقہ کار کے لیے ایک مختلف موازنہ پاس کرتے ہیں sort()، جیسے کہ sortByCumulativeWordLength، تو ہمیں ایک مختلف نتیجہ ملے گا:
  1. if then else
  2. Dota GTA5 Halo
  3. I really love Java
اب arrays کو صف کے الفاظ میں حروف کی کل تعداد کے حساب سے ترتیب دیا گیا ہے۔ پہلی صف میں، 10 حروف ہیں، دوسرے میں - 12، اور تیسرے میں - 15۔ اگر ہمارے پاس صرف ایک موازنہ ہے، تو ہمیں اس کے لیے الگ متغیر کا اعلان کرنے کی ضرورت نہیں ہے۔ sort()اس کے بجائے، ہم طریقہ کار پر کال کے وقت ہی ایک گمنام کلاس بنا سکتے ہیں ۔ کچھ اس طرح:

String[] array1 = {"Dota", "GTA5", "Halo"}; 
String[] array2 = {"I", "really", "love", "Java"}; 
String[] array3 = {"if", "then", "else"}; 

List<String[]> arrays = new ArrayList<>(); 

arrays.add(array1); 
arrays.add(array2); 
arrays.add(array3); 

arrays.sort(new Comparator<String[]>() { 
    @Override 
    public int compare(String[] o1, String[] o2) { 
        return o1.length - o2.length; 
    } 
});
ہمیں وہی نتیجہ ملے گا جو پہلے کیس میں آیا تھا۔ ٹاسک 1۔ اس مثال کو دوبارہ لکھیں تاکہ یہ صفوں کو ہر صف میں الفاظ کی تعداد کے صعودی ترتیب میں نہیں بلکہ نزولی ترتیب میں ترتیب دے۔ یہ سب ہم پہلے ہی جانتے ہیں۔ ہم چیزوں کو طریقوں تک منتقل کرنا جانتے ہیں۔ اس وقت ہمیں جس چیز کی ضرورت ہے اس پر انحصار کرتے ہوئے، ہم مختلف اشیاء کو ایک طریقہ کار میں منتقل کر سکتے ہیں، جو اس کے بعد اس طریقہ کو استعمال کرے گا جسے ہم نے نافذ کیا ہے۔ یہ سوال پیدا کرتا ہے: دنیا میں ہمیں یہاں لیمبڈا اظہار کی ضرورت کیوں ہے؟  کیونکہ لیمبڈا اظہار ایک ایسی چیز ہے جس کا بالکل ایک طریقہ ہے۔ ایک "طریقہ آبجیکٹ" کی طرح۔ کسی چیز میں پیک کیا ہوا طریقہ۔ اس میں صرف ایک قدرے غیر مانوس نحو ہے (لیکن اس پر بعد میں مزید)۔ آئیے اس کوڈ پر ایک اور نظر ڈالیں:

arrays.sort(new Comparator<String[]>() { 
    @Override 
    public int compare(String[] o1, String[] o2) { 
        return o1.length - o2.length; 
    } 
});
یہاں ہم اپنی صفوں کی فہرست لیتے ہیں اور اس کے sort()طریقہ کار کو کہتے ہیں، جس میں ہم ایک ہی compare()طریقہ کے ساتھ موازنہ کرنے والے آبجیکٹ کو پاس کرتے ہیں (اس کے نام سے ہمیں کوئی فرق نہیں پڑتا ہے - آخر کار، یہ اس آبجیکٹ کا واحد طریقہ ہے، اس لیے ہم غلط نہیں ہو سکتے)۔ اس طریقہ میں دو پیرامیٹرز ہیں جن کے ساتھ ہم کام کریں گے۔ اگر آپ IntelliJ IDEA میں کام کر رہے ہیں، تو آپ نے شاید یہ کوڈ کو مندرجہ ذیل طور پر نمایاں طور پر کم کرنے کی پیشکش کو دیکھا ہے:

arrays.sort((o1, o2) -> o1.length - o2.length);
یہ چھ لائنوں کو ایک مختصر سے کم کر دیتا ہے۔ 6 لائنوں کو ایک مختصر کے طور پر دوبارہ لکھا جاتا ہے۔ کچھ غائب ہوگیا، لیکن میں ضمانت دیتا ہوں کہ یہ کوئی اہم چیز نہیں تھی۔ یہ کوڈ بالکل اسی طرح کام کرے گا جیسا کہ کسی گمنام کلاس کے ساتھ ہوتا ہے۔ ٹاسک 2۔ لیمبڈا ایکسپریشن کا استعمال کرتے ہوئے ٹاسک 1 کے حل کو دوبارہ لکھنے کا اندازہ لگائیں (کم از کم، IntelliJ IDEA سے اپنی گمنام کلاس کو لیمبڈا ایکسپریشن میں تبدیل کرنے کے لیے کہیں)۔

آئیے انٹرفیس کے بارے میں بات کرتے ہیں۔

اصولی طور پر، ایک انٹرفیس محض تجریدی طریقوں کی ایک فہرست ہے۔ جب ہم ایک کلاس بناتے ہیں جو کچھ انٹرفیس کو لاگو کرتا ہے، تو ہماری کلاس کو انٹرفیس میں شامل طریقوں کو نافذ کرنا ہوگا (یا ہمیں کلاس کو خلاصہ بنانا ہوگا)۔ بہت سے مختلف طریقوں کے ساتھ انٹرفیس ہیں (مثال کے طور پر،  List)، اور صرف ایک طریقہ کے ساتھ انٹرفیس ہیں (مثال کے طور پر، Comparatorیا Runnable)۔ ایسے انٹرفیس ہیں جن کا ایک طریقہ نہیں ہے (نام نہاد مارکر انٹرفیس جیسے کہ Serializable)۔ انٹرفیس جن کا صرف ایک طریقہ ہوتا ہے انہیں فنکشنل انٹرفیس بھی کہا جاتا ہے ۔ جاوا 8 میں، وہ ایک خاص تشریح کے ساتھ نشان زد بھی ہیں: @FunctionalInterface. یہ واحد طریقہ والے انٹرفیس ہیں جو لیمبڈا اظہار کے لیے ہدف کی اقسام کے طور پر موزوں ہیں۔ جیسا کہ میں نے اوپر کہا، ایک لیمبڈا اظہار ایک ایسا طریقہ ہے جو کسی چیز میں لپٹا ہوا ہے۔ اور جب ہم ایسی چیز کو پاس کرتے ہیں، تو ہم بنیادی طور پر اس واحد طریقہ کو پاس کر رہے ہوتے ہیں۔ یہ پتہ چلتا ہے کہ ہمیں پرواہ نہیں ہے کہ طریقہ کیا کہا جاتا ہے. صرف وہی چیزیں جو ہمارے لیے اہمیت رکھتی ہیں وہ ہیں طریقہ کار کے پیرامیٹرز اور یقیناً طریقہ کار کا باڈی۔ جوہر میں، ایک لیمبڈا اظہار ایک فنکشنل انٹرفیس کا نفاذ ہے۔ جہاں بھی ہم ایک ہی طریقہ کے ساتھ انٹرفیس دیکھتے ہیں، ایک گمنام کلاس کو لیمبڈا کے طور پر دوبارہ لکھا جا سکتا ہے۔ اگر انٹرفیس میں ایک سے زیادہ یا کم طریقہ ہے، تو ایک lambda اظہار کام نہیں کرے گا اور ہم اس کے بجائے ایک گمنام کلاس یا یہاں تک کہ ایک عام کلاس کی مثال استعمال کریں گے۔ اب لیمبڈاس میں تھوڑا سا کھودنے کا وقت ہے۔ :)

نحو

عمومی ترکیب کچھ اس طرح ہے:

(parameters) -> {method body}
یعنی، طریقہ کے پیرامیٹرز کے ارد گرد قوسین، ایک "تیر" (ایک ہائفن اور زیادہ سے زیادہ نشان کے ذریعہ تشکیل دیا گیا ہے)، اور پھر ہمیشہ کی طرح منحنی خطوط وحدانی میں میتھڈ باڈی۔ پیرامیٹرز انٹرفیس کے طریقہ کار میں بیان کردہ ان سے مطابقت رکھتے ہیں۔ اگر متغیر کی قسمیں مرتب کرنے والے کے ذریعہ غیر واضح طور پر طے کی جاسکتی ہیں (ہمارے معاملے میں، یہ جانتا ہے کہ ہم سٹرنگ اری کے ساتھ کام کر رہے ہیں، کیونکہ ہمارا Listآبجیکٹ ] کا استعمال کرتے ہوئے ٹائپ کیا جاتا ہے String[)، تو آپ کو ان کی اقسام کی نشاندہی کرنے کی ضرورت نہیں ہے۔
اگر وہ مبہم ہیں تو قسم کی نشاندہی کریں۔ اگر ضرورت نہ ہو تو IDEA اسے خاکستری رنگ دے گا۔
آپ اس اوریکل ٹیوٹوریل میں اور کہیں اور پڑھ سکتے ہیں ۔ اسے " ٹارگٹ ٹائپنگ " کہا جاتا ہے۔ آپ متغیرات کو جو چاہیں نام دے سکتے ہیں — آپ کو انٹرفیس میں بیان کردہ وہی نام استعمال کرنے کی ضرورت نہیں ہے۔ اگر کوئی پیرامیٹرز نہیں ہیں، تو صرف خالی قوسین کی نشاندہی کریں۔ اگر صرف ایک پیرامیٹر ہے تو بغیر کسی قوسین کے متغیر نام کی نشاندہی کریں۔ اب جب کہ ہم پیرامیٹرز کو سمجھتے ہیں، اب وقت آگیا ہے کہ لیمبڈا ایکسپریشن کے باڈی پر بات کی جائے۔ گھوبگھرالی منحنی خطوط وحدانی کے اندر، آپ کوڈ اسی طرح لکھتے ہیں جیسے آپ ایک عام طریقہ کے لیے لکھتے ہیں۔ اگر آپ کا کوڈ ایک لائن پر مشتمل ہے، تو آپ گھوبگھرالی منحنی خطوط وحدانی کو مکمل طور پر (if-statements اور for-loops کی طرح) چھوڑ سکتے ہیں۔ اگر آپ کا سنگل لائن لیمبڈا کچھ لوٹاتا ہے، تو آپ کو returnبیان شامل کرنے کی ضرورت نہیں ہے۔ لیکن اگر آپ گھوبگھرالی منحنی خطوط وحدانی استعمال کرتے ہیں، تو آپ کو واضح طور پر ایک returnبیان شامل کرنا چاہیے، جیسا کہ آپ ایک عام طریقہ میں کرتے ہیں۔

مثالیں

مثال 1۔

() -> {}
سب سے آسان مثال۔ اور سب سے زیادہ بے معنی :) چونکہ اس سے کچھ نہیں ہوتا۔ مثال 2۔

() -> ""
ایک اور دلچسپ مثال۔ یہ کچھ نہیں لیتا ہے اور ایک خالی تار واپس کرتا ہے ( returnچھوڑ دیا جاتا ہے، کیونکہ یہ غیر ضروری ہے)۔ یہاں ایک ہی چیز ہے، لیکن اس کے ساتھ return:

() -> { 
    return ""; 
}
مثال 3۔ "ہیلو، ورلڈ!" لیمبڈاس کا استعمال کرتے ہوئے

() -> System.out.println("Hello, World!")
returnیہ کچھ نہیں لیتا ہے اور کچھ بھی نہیں لوٹاتا ہے (ہم کال کرنے سے پہلے نہیں ڈال سکتے System.out.println()، کیونکہ println()طریقہ کی واپسی کی قسم ہے void)۔ یہ صرف سلام کو ظاہر کرتا ہے۔ یہ انٹرفیس کے نفاذ کے لیے مثالی ہے Runnable۔ مندرجہ ذیل مثال زیادہ مکمل ہے:

public class Main { 
    public static void main(String[] args) { 
        new Thread(() -> System.out.println("Hello, World!")).start(); 
    } 
}
یا اس طرح:

public class Main { 
    public static void main(String[] args) { 
        Thread t = new Thread(() -> System.out.println("Hello, World!")); 
        t.start();
    } 
}
یا ہم لیمبڈا اظہار کو بطور Runnableآبجیکٹ محفوظ کر سکتے ہیں اور پھر اسے Threadکنسٹرکٹر کو دے سکتے ہیں۔

public class Main { 
    public static void main(String[] args) { 
        Runnable runnable = () -> System.out.println("Hello, World!"); 
        Thread t = new Thread(runnable); 
        t.start(); 
    } 
}
آئیے اس لمحے کو قریب سے دیکھتے ہیں جب لیمبڈا اظہار کو متغیر میں محفوظ کیا جاتا ہے۔ انٹرفیس Runnableہمیں بتاتا ہے کہ اس کی اشیاء کا ایک public void run()طریقہ ہونا چاہیے۔ انٹرفیس کے مطابق، runطریقہ کوئی پیرامیٹرز لیتا ہے. اور یہ کچھ بھی نہیں لوٹاتا ہے، یعنی اس کی واپسی کی قسم ہے void۔ اس کے مطابق، یہ کوڈ ایک ایسے طریقہ کے ساتھ ایک آبجیکٹ بنائے گا جو کچھ بھی نہیں لیتا یا واپس نہیں کرتا ہے۔ یہ Runnableانٹرفیس کے run()طریقہ کار سے بالکل میل کھاتا ہے۔ اسی لیے ہم اس لیمبڈا اظہار کو Runnableمتغیر میں رکھنے کے قابل تھے۔  مثال 4۔

() -> 42
ایک بار پھر، یہ کچھ نہیں لیتا، لیکن یہ نمبر 42 لوٹاتا ہے۔ اس طرح کے لیمبڈا اظہار کو متغیر میں رکھا جا سکتا ہے Callable، کیونکہ اس انٹرفیس میں صرف ایک طریقہ ہے جو کچھ اس طرح لگتا ہے:

V call(),
V واپسی کی قسم  کہاں  ہے (ہمارے معاملے میں، int) اس کے مطابق، ہم مندرجہ ذیل طور پر لیمبڈا اظہار کو محفوظ کر سکتے ہیں:

Callable<Integer> c = () -> 42;
مثال 5. ایک لیمبڈا اظہار جس میں کئی سطریں شامل ہیں۔

() -> { 
    String[] helloWorld = {"Hello", "World!"}; 
    System.out.println(helloWorld[0]); 
    System.out.println(helloWorld[1]); 
}
ایک بار پھر، یہ ایک لیمبڈا اظہار ہے جس میں کوئی پیرامیٹرز اور voidواپسی کی قسم نہیں ہے (کیونکہ کوئی returnبیان نہیں ہے)۔  مثال 6

x -> x
یہاں ہم ایک xمتغیر لیتے ہیں اور اسے واپس کرتے ہیں۔ براہ کرم نوٹ کریں کہ اگر صرف ایک پیرامیٹر ہے، تو آپ اس کے ارد گرد کے قوسین کو چھوڑ سکتے ہیں۔ یہاں ایک ہی چیز ہے، لیکن قوسین کے ساتھ:

(x) -> x
اور یہاں ایک واضح واپسی کے بیان کے ساتھ ایک مثال ہے:

x -> { 
    return x;
}
یا قوسین اور واپسی کے بیان کے ساتھ اسے پسند کریں:

(x) -> { 
    return x;
}
یا قسم کے واضح اشارے کے ساتھ (اور اس طرح قوسین کے ساتھ):

(int x) -> x
مثال 7

x -> ++x
ہم xاسے لے کر واپس کرتے ہیں، لیکن صرف 1 شامل کرنے کے بعد۔ آپ اس لیمبڈا کو اس طرح دوبارہ لکھ سکتے ہیں:

x -> x + 1
دونوں صورتوں میں، ہم بیان کے ساتھ پیرامیٹر اور طریقہ کار کے ارد گرد قوسین کو چھوڑ دیتے ہیں return، کیونکہ وہ اختیاری ہیں۔ قوسین کے ساتھ ورژن اور واپسی کا بیان مثال 6 میں دیا گیا ہے۔ مثال 8

(x, y) -> x % y
ہم بقیہ تقسیم بذریعہ لیتے ہیں xاور واپس کرتے ہیں ۔ پیرامیٹرز کے ارد گرد قوسین یہاں درکار ہیں۔ وہ صرف اس وقت اختیاری ہیں جب صرف ایک پیرامیٹر ہو۔ یہاں یہ اقسام کے واضح اشارے کے ساتھ ہے: yxy

(double x, int y) -> x % y
مثال 9

(Cat cat, String name, int age) -> {
    cat.setName(name); 
    cat.setAge(age); 
}
ہم ایک Catچیز، ایک Stringنام، اور ایک int عمر لیتے ہیں۔ خود طریقہ میں، ہم بلی پر متغیرات قائم کرنے کے لیے گزرے ہوئے نام اور عمر کا استعمال کرتے ہیں۔ چونکہ ہمارا catاعتراض ایک حوالہ کی قسم ہے، اس کو لیمبڈا ایکسپریشن سے باہر تبدیل کر دیا جائے گا (اسے پاس شدہ نام اور عمر ملے گی)۔ یہاں ایک قدرے زیادہ پیچیدہ ورژن ہے جو اسی طرح کے لیمبڈا کا استعمال کرتا ہے:

public class Main { 

    public static void main(String[] args) { 
        // Create a cat and display it to confirm that it is "empty" 
        Cat myCat = new Cat(); 
        System.out.println(myCat);
 
        // Create a lambda 
        Settable<Cat> s = (obj, name, age) -> { 
            obj.setName(name); 
            obj.setAge(age); 

        }; 

        // Call a method to which we pass the cat and lambda 
        changeEntity(myCat, s); 

        // Display the cat on the screen and see that its state has changed (it has a name and age) 
        System.out.println(myCat); 

    } 

    private static <T extends HasNameAndAge>  void changeEntity(T entity, Settable<T> s) { 
        s.set(entity, "Smokey", 3); 
    }
}

interface HasNameAndAge { 
    void setName(String name); 
    void setAge(int age); 
}

interface Settable<C extends HasNameAndAge> { 
    void set(C entity, String name, int age); 
}

class Cat implements HasNameAndAge { 
    private String name; 
    private int age; 

    @Override 
    public void setName(String name) { 
        this.name = name;
    }

    @Override
    public void setAge(int age) {
        this.age = age; 
    } 

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' + 
                ", age=" + age + 
                '}';
    }
}
نتیجہ:

Cat{name='null', age=0}
Cat{name='Smokey', age=3}
جیسا کہ آپ دیکھ سکتے ہیں، Catآبجیکٹ کی ایک حالت تھی، اور پھر ریاست بدل گئی جب ہم نے لیمبڈا اظہار استعمال کیا۔ لیمبڈا کے تاثرات جنرک کے ساتھ بالکل یکجا ہوتے ہیں۔ اور اگر ہمیں ایک ایسی کلاس بنانے کی ضرورت ہے Dogجو لاگو بھی کرتی ہے ، تو ہم  لیمبڈا ایکسپریشن کو تبدیل کیے بغیر طریقہ کار میں HasNameAndAgeوہی آپریشن کر سکتے ہیں ۔ ٹاسک 3۔ ایک ایسے طریقہ کے ساتھ ایک فنکشنل انٹرفیس لکھیں جو ایک نمبر لیتا ہے اور بولین ویلیو دیتا ہے۔ اس طرح کے انٹرفیس کا نفاذ ایک لیمبڈا ایکسپریشن کے طور پر لکھیں جو درست لوٹاتا ہے اگر پاس شدہ نمبر کو 13 سے تقسیم کیا جا سکتا ہے۔ ٹاسک 4۔ ایک فنکشنل انٹرفیس کو اس طریقہ کے ساتھ لکھیں جو دو سٹرنگ لے اور ایک سٹرنگ بھی واپس کرے۔ اس طرح کے انٹرفیس کا نفاذ ایک لیمبڈا ایکسپریشن کے طور پر لکھیں جو لمبی تار کو لوٹاتا ہے۔ ٹاسک 5۔ اس طریقے کے ساتھ ایک فنکشنل انٹرفیس لکھیں جس میں تین فلوٹنگ پوائنٹ نمبر ہوں: a، b، اور c اور ایک فلوٹنگ پوائنٹ نمبر بھی لوٹاتے۔ اس طرح کے انٹرفیس کا نفاذ ایک لیمبڈا اظہار کے طور پر لکھیں جو امتیاز کو لوٹاتا ہے۔ اگر آپ بھول گئے ہیں، تو یہ ہے ۔ ٹاسک 6۔ ٹاسک 5 سے فنکشنل انٹرفیس کا استعمال کرتے ہوئے، ایک لیمبڈا ایکسپریشن لکھیں جو کہ کا نتیجہ لوٹاتا ہے ۔ جاوا میں لیمبڈا اظہار کی وضاحت۔ مثالوں اور کاموں کے ساتھ۔ حصہ 2 Dogmain()D = b^2 — 4aca * b^c
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION