آموزش قدم به قدم جاوا – قسمت آخر

عبارات لامبدا (Lambda Expressions)

عبارات لامبدا در JDK 8 به جاوا اضافه شدند. طرز کار عبارات لامبدا از دو قسمت تشکیل می‌شود: اول خود عبارت لامبدا و دوم functional interfaceها. حال به توضیح هر یک از این موارد می‌پردازیم:

عبارت لامبدا: یک عبارت لامبدا در واقع یک متد ناشناس (بدون نام) است. البته این متد ناشناس به تنهایی قابل اجرا نیست و برای پیاده‌سازی متدی که در یک functional interface تعریف‌شده به کار می‌رود. بنابراین می‌توان گفت که یک عبارت لامبدا نوعی کلاس ناشناس به حساب می‌آید. به عبارات لامبدا Closure نیز گفته می‌شود.

Functional Interface: اینترفیسی است که فقط و فقط یک متد در آن تعریف شده است (متد abstract یا همان متدهای معمولی که در اینترفیس‌ها تعریف می‌شوند). این متد معمولا بیانگر هدف اصلی اینترفیس است بنابراین یک اینترفیس تابعی بیانگر یک عمل (action) خاص است.

مثلا اینترفیس Runnable یک اینترفیس تابعی است چون فقط یک متد به نام run() دارد و متد run عمل اینترفیس Runnable را تعریف می‌کند.

نکته: به اینترفیس‌های تابعی SAM type نیز گفته می‌شود که SAM مخفف Single Abstract Method است.

مبانی عبارات لامبدا

همراه با عبارات لامبدا یک عملگر جدید نیز به زبان جاوا اضافه شده است که به آن عملگر لامبدا (lambda operator) یا عملگر پیکان (arrow operator) نیز گفته می‌شود و به شکل -> نوشته می‌شود. این عملگر یک عبارت لامبدا را به دو بخش تقسیم می‌کند: بخش سمت چپ پارامترهای موردنیاز عبارت لامبدا و بخش سمت راست بدنه عبارت لامبدا را مشخص می‌کند که بدنه عبارت لامبدا بیانگر کاری است که عبارت لامبدا انجام می‌دهد.

دو نوع بدنه لامبدا در جاوا وجود دارد: نوع اول فقط شامل یک عبارت و نوع دوم شامل بلاکی از کدها می‌باشد.

کد زیر نمونه‌ای از ساده‌ترین عبارت لامبدایی که می‌توان نوشت است:

این عبارت لامبدا عدد 13 را برگردانده و هیچ پارامتری دریافت نمی‌کند و بنابراین لیست پارامترهای آن خالی است.

عبارت لامبدایی که دیدید را می‌توان به صورت زیر در قالب یک متد معمولی نوشت:

البته test یک نام فرضی می‌باشد و همانطور که گفتیم عبارات لامبدا بدون نام هستند.

هر نوع داده معتبری می‌تواند به عنوان نوع داده برگشتی یک عبارت لامبدا تعریف شود.

نمونه‌ای دیگر از یک عبارت لامبدا که یک پارامتر دریافت می‌کند:

این عبارت لامبدا عدد 1 را تقسیم بر مقدار پارامتر دریافتی خود کرده و نتیجه را برمی‌گرداند.

نکته: اگر یک عبارت لامبدا فقط یک پارامتر دریافت کند می‌توان پرانتزی که آن را احاطه کرده است را حذف کرد. مثلا عبارت لامبدای بالا را به صورت زیر نیز ‌می‌توان نوشت:

اینترفیس‌های تابعی (Functional Interfaces)

همانطور که گفته شد یک اینترفیس تابعی اینترفیسی است که فقط یک متد abstract داشته باشد. اگرچه اینترفیس‌های تابعی می‌توانند شامل متدهای static یا default نیز باشند اما فقط و فقط یک متد abstract باید در آن‌ها وجود داشته باشد.

یک عبارت لامبدا در واقع پیاده‌سازی متد abstract‌ی است که در یک اینترفیس تابعی تعریف شده است.

به اینترفیس زیر دقت کنید:

در این مثال متد getNumber یک متد abstract است (اگرچه با کلمه abstract تعریف نشده اما در یک اینترفیس متدی که نه static باشد و نه default به صورت ضمنی abstract است). پس این اینترفیس یک اینترفیس تابعی است و عملکرد آن با متد getNumber تعریف شده است.

همانطور که گفتیم یک عبارت لامبدا به تنهایی اجرا نمی‌شود. در ادامه یک مثال نحوه استفاده از عبارات لامبدا را مشاهده خواهید کرد.

مثال: انتساب یک عبارت لامبدا به یک متغیر از نوع اینترفیس تابعی مربوط به عبارت لامبدا.

اینترفیس MyNumber را که پیش‌تر کد آن را دیدیم در نظر بگیرید:

در این کد ابتدا متغیری از نوع اینترفیس تابعی MyNumber تعریف کردیم سپس یک عبارت لامبدا به این متغیر نسبت دادیم. این عبارت لامبدا در واقع پیاده‌سازی متد getNumber که در اینترفیس تابعی MyNumber وجود دارد می‌باشد.

حال که متد getNumber را با استفاده از یک عبارت لامبدا پیاده‌سازی کردیم و به متغیر myNum نسبت دادیم می‌توانیم با فراخوانی متد getNumber از متغیر myNum از عبارت لامبدایی که نوشتیم استفاده کنیم.

این کار را با استفاده از کلاس‌های ناشناس نیز می‌توان انجام داد اما همانطور که دیدید عبارات لامبدا نیاز به کدنویسی کمتری نسبت که کلاس‌های ناشناس دارند.

نکته مهمی که باید به آن توجه داشت این است که عبارت لامبدایی که برای پیاده‌سازی یک متد در یک اینترفیس تابعی می‌نویسیم باید با آن متد سازگاری داشته باشد. مثلا اگر عبارت لامبدایی که در مثال قبل نوشتیم به جای عدد 13 عدد 13.5 را بر می‌گرداند برنامه اجرا نمی‌شد چون نوع داده بازگشتی متد getNumber از نوع عدد صحیح int است. همچنین چون متد getNumber هیچ پارامتری دریافت نمی‌کرد پس عبارت لامبدایی که نوشتیم نیز باید بدون پارامتر باشد.

با توجه به نوع و تعداد پارامترها و نوع داده بازگشتی می‌توان پیاده سازی‌های مختلفی از یک functional interface را نوشت.

مثال: اینترفیسی به نام NumberTest داریم که متدی به نام test به صورت زیر در آن تعریف شده است:

حال سه عبارت لامبدا که هرکدام پیاده‌سازی مختلفی دارند می‌نویسیم:

هر سه عبارت لامبدایی که نوشتیم دو پارامتر از نوع int دریافت می‌کنند و یک مقدار boolean برمی‌گردانند پس با اینترفیس NumberTest سازگاری دارند اگرچه هر کدام پیاده‌سازی‌های مختلفی دارند.

ضمنا توجه داشته باشید در یک عبارت لامبدا نام پارامترها حتما نباید با نامی که برای پارامترها در اینترفیس نوشته شده یکی باشد. فقط نوع پارامترها، تعداد پارامترها و نوع داده بازگشتی باید با اینترفیس تابعی مطابقت داشته باشد. همچنین پارامترهای متد یک اینترفیس تابعی می‌تواند هم از نوع Primitive و هم از نوع Reference باشد.

عبارات لامبدای بلاکی (Block Lambda Expressions)

عبارات لامبدایی که تا الان بررسی کردیم همگی از یک عبارت (expression) تشکیل می‌شدند. به این نوع عبارات لامبدا expression lambda نیز می‌گویند. اما گاهی اوقات نیاز به نوشتن بیش از یک عبارت در بدنه لامبدا می‌باشد که در این صورت از لامبداهای بلاکی (block lambda) استفاده می‌کنیم.

مثال: اینترفیس مثال قبل را به این صورت تغییر دادیم:

حال یک لامبدای بلاکی می‌نویسیم که دو پارامتر این متد را با هم مقایسه کند و عبارت مناسبی را برگرداند:

همانطور که دیدید در لامبداهای بلاکی باید مقدار بازگشتی را صراحتا با return مشخص کرد.

ارجاع به متد (Method Reference)

قابلیت مهم دیگری به نام ارجاع به متد وجود دارد که به نوعی به عبارات لامبدا مربوط است. با این قابلیت می‌توان به یک متد ارجاع داد بدون آنکه نیاز به فراخوانی آن باشد. شباهت ارجاع به متد به عبارات لامبدا از این جهت است که هر دوی آن‌ها باید با یک اینترفیس تابعی تطبیق داشته باشند.

در عبارات لامبدا یک متد ناشناس می‌نوشتیم که به عنوان کلاس ناشناسی که یک اینترفیس تابعی را پیاده‌سازی می‌کند عمل می‌کرد. ارجاع به متدها نیز همین امکان را به ما می‌دهند با این تفاوت که متدهایی از قبل نوشته شده‌اند و ما فقط به آن‌ها ارجاع می‌دهیم.

ارجاع به متدهای استاتیک (Reference to Static Methods)

مثال: اینترفیس NumberTest از مثال قبل را داریم:

کلاسی به نام Tests داریم که شامل سه متد استاتیک به صورت زیر است:

در کلاس دیگری متدی به صورت زیر داریم:

همانطور که می‌بینید این متد پارامتری از نوع اینترفیس NumberTest دریافت می‌کند و دو پارامتر دیگر از نوع int. همانطور که از قسمت اینترفیس‌ها آموختید وقتی متغیری از نوع یک اینترفیس داریم باید به آن شیئی از کلاسی نسبت دهیم که آن اینترفیس را پیاده‌سازی می‌کند.

به چهار روش می‌توانیم به پارامتر t مقدار دهیم. البته روش‌های سوم و چهارم فقط در مورد اینترفیس‌های تابعی کاربرد دارند:

1 – ساخت یک کلاس و پیاده‌سازی اینترفیس NumberTest در آن و سپس ساخت شی از آن کلاس و دادن آن به متد testNumbers

2 – ساخت یک کلاس ناشناس که اینترفیس NumberTest را پیاده‌سازی کند و دادن آن به متد testNumbers

3 – نوشتن یک عبارت لامبدا که با متد موجود در اینترفیس NumberTest سازگار باشد (از لحاظ نوع و تعداد پارامترها و نوع داده بازگشتی)

4 – روش چهارم این است که می‌توانیم متدی به عنوان t به متد testNumbers بدهیم که این متد با متد موجود در اینترفیس NumberTest سازگاری داشته باشد.

از آنجایی که تمام متدهای موجود در کلاس Tests با متد اینترفیس NumberTest سازگاری دارند. چون متدهای موجود در کلاس Tests به صورت استاتیک تعریف شده‌اند بنابراین باید به صورت زیر به آن‌ها ارجاع دهیم:

ClassName::methodName

عملگر :: برای ارجاع به متدها ایجاد شده است. پس به عنوان مثال می‌توانیم متد testNumbers را به صورت زیر فراخوانی کنیم:

که در نتیجه متد isBigger با پارامترهای 10 و 5 فراخوانی شده و مقداری برگردانده می‌شود.

البته مانند عبارات لامبدا می‌توانیم ارجاع به متدها را به صورت زیر نیز استفاده کنیم:

به طور کلی طبق روش چهارم که در بالا اشاره شد به هر متغیر از نوع یک اینترفیس تابعی می‌توانیم ارجاع به متدی دهیم که با متد موجود در آن اینترفیس تابعی سازگاری داشته باشد.

نکته: ارجاع به متدها در پشت صحنه تبدیل به شیئی می‌شوند که اینترفیس تابعی مورد نظر در آن پیاده‌سازی شده است.

نکته: ارجاع به متدهای نمونه (Instance Methods) نیز مانند ارجاع به متدهای استاتیک است با این تفاوت که به جای نام کلاس باید نام شیئی که از کلاس ایجاد شده را بنویسیم.

مصطفی نصیری

دانشجوی نرم افزار هستم و علاقه شدیدی به برنامه نویسی مخصوصا با زبان جاوا دارم! در حال حاضر تمرکزم روی اندرویده. دوست دارم چیزایی که یاد میگیرم رو با بقیه به اشتراک بگذارم :)

همچنین ممکن است دوست داشته باشید ...

۹ واکنش

  1. مسلم دریس گفت:

    باید ۲-۳ مرتبه دیگه بخونمش. یکم پیچیده بود! و اینکه کاربردش هم به نظر میاد کم باشه!

    ولی خب خیلی ممنونم از توضیحات کاملتون مدتی بود میخواستم با این عبارت آشنا بشم که منبع فارسی خوبی پیدا نمیکردم.

    • مصطفی نصیری گفت:

      خواهش میکنم اگر سوالی داشتید میتونید همینجا بپرسید. درباره کاربردشون هم میشه گفت که خیلی پرکاربرد نیستند اما بدون استفاده هم نیستند. به عنوان مثال اگر برنامه نویسی اندروید کار کنید میتونید برای تعریف رویداد کلیک از عبارات لامبدا به جای Anonymous Class استفاده کنید. در بحث Functional Programming هم از عبارات لامبدا استفاده میشه.
      نمونه دیگه از کاربرد عبارات لامبدا هم در Stream API هست که در جاوا ۸ معرفی شده.

      • مسلم دریس گفت:

        بله جدیدا متوجه شدم که برای استفاده از رویداد ها بکارگیری لامبدا چقدر میتونه کد رو واضح تر و کوتاه تر بکنه!

        در کل جاوای ۸ ساختار های بسیار بسیار خوبی رو اضافه کرده که متاسفانه درحال حاضر تمام قابلیت هاش در اندروید قابل استفاده نیست. مثلا اضافه شدن forEach به کالکشن ها فاق العاده بود که الآن نمیتونیم ازش استفاده کنیم!!!

  2. مسلم دریس گفت:

    همین الآن متوجه شدم که کامپایلر”جک” که برای استفاده از جاوا۸ در اندروید باید حتما حضور داشته باشه یه باگی داره که فعلا هم برطرف نشده!
    جک برای دیکد کردن متن از سیستم پیشفرض ماشین جاوا استفاده میکنه (windows-1252) در صورتی که متن ما با UTF-8 انکود میشه و همین باعث میشه استرینگ های فارسی در کد جاوای ما در برنامه ناخوانا باشند و خب باید حتما در یک فایل xml اکسترکتشون کنید و …
    متاسفانه هم هنوز گریدل به ما اجازه تغییر فرمت دیکود رو نمیده و باید منتظر بمونیم ببینیم گوگل چکار میکنه!!!

  3. سجاد گفت:

    سلام . دوره ی جاوا تموم شد…
    حالا اندروید رو شروع کنین… چاوا رو که همه بلدن ، اندروید رو بگین ممنون

    • مصطفی نصیری گفت:

      سلام. فعلا قصدی برای آموزش اندروید ندارم به دلیل اینکه آموزش اندروید بسیار گسترده و برای من بسیار زمان بر هست و حداقل به این زودی توانایی تولیدش رو ندارم. اگه همه جاوا رو بلد باشن که خیلی خوبه 🙂 چون من به شخصه هنوز نمی تونم بگم جاوا رو بلدم.
      موفق باشید

  4. ممنون از مطالبتون و اموزش مفید واقع شد اگه ممکنه یه مقدار در مورد php بنویسید با تشکر

  5. نیما گفت:

    سلام یک سوال
    براتون مقدوره اموزش هاتون رو PDF کنید تا بتونیم افلاین استفاده کنیم ؟؟؟؟

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *