نسخههای قدیمیتر جاوا از الگوی شیگرا (Object-Oriented) همراه با سبک دستوری (Imperative) برنامهنویسی پشتیبانی میکردند. از جاوا ۸ به بعد، میتوانید سبک تابعی (Functional) برنامهنویسی را هم در کدتان ترکیب کنید. اگر پروژهی شما در دوران جاوا ۷ یا قدیمیتر شروع شده، یا توسعهدهندگانی که روی آن کار کردهاند عمدتاً با نسخههای قدیمیتر جاوا آشنا بودهاند، احتمالاً پر از کد نوشتهشده به سبک دستوری است.
در سبک دستوری (Imperative)، به کامپایلر میگوییم چه کند و همچنین چگونه انجام دهد. در سبک تابعی (Functional)، ماهیتاً توصیفی (Declarative) عمل میکنیم؛ یعنی فقط میگوییم چه کند و جزئیات «چگونگی» یا پیادهسازی را به کتابخانههای زیرساختی واگذار میکنیم. کد دستوری شاید نوشتنش آسانتر باشد چون بیشتر ما با آن بسیار آشنا هستیم. اما به مرور کد حجیم، پیچیده و سختخوان میشود. کد تابعی در ابتدا شاید دشوار به نظر برسد — بیشتر به این دلیل که اکثر برنامهنویسان کمتر با آن آشنا هستند — اما در مجموع خواندن، فهمیدن و تغییر دادنش آسانتر است. با تمرین، نوشتنش هم راحتتر میشود.
در این مجموعه آموزشی، تعدادی از الگوهای رایج کد سبک دستوری را بررسی میکنیم و معادل تابعی هر کدام را پیدا میکنیم. وقتی مشغول کار روی کد پروژهتان هستید و آمادهاید باگی را رفع کنید یا قابلیتی اضافه نمایید، ممکن است بازنویسی (Refactor) بخشهایی از کد دستوری به سبک تابعی مفید باشد. میتوانید از این آموزش بهعنوان راهنما برای پیدا کردن نگاشتهای سبک دستوری به تابعی در موقعیتهای رایج استفاده کنید.
در این آموزش روی حلقههای ساده تمرکز داریم.
---بیایید با حلقهی سنتی for شروع کنیم؛ جایی که یک عمل را روی مقادیر یک اندیس در یک بازه اجرا میکنیم:
for(int i = 0; i < 5; i++) {
IO.println(i);
}
در کد بالا، هستهی اصلی همان بازه است — از 0 تا یکی کمتر از 5. اما سربار (Ceremony)، یعنی نویز اضافی، از بخش چگونگی (How) میآید: سینتکس حلقه بهعلاوهی عملیات افزایشی (Increment) روی متغیر اندیس i. میتوانیم هسته را حفظ کنیم و سربار را با تبدیل کد به سبک تابعی حذف نماییم.
اگر میخواهید از سبک تابعی برای نوشتن این حلقهی for استفاده کنید، کار بسیار سادهای است و سرنخ همان عبارت قبل از کد است: اندیس روی یک بازه (index over a range). چون روی یک بازه حلقه میزنیم، متد range از IntStream معادل مستقیم آن است.
import java.util.stream.IntStream;
...
IntStream.range(0, 5)
.forEach(i -> IO.println(i));
میتوانید این را با استفاده از ارجاع متد (Method Reference) برای متد println مختصرتر هم بنویسید:
import java.util.stream.IntStream;
...
IntStream.range(0, 5)
.forEach(IO::println);
کد سبک تابعی مختصرتر است، خواندنش آسانتر است و قصد (Intention) در این نسخه نسبت به نسخهی دستوری شفافتر است.
اما اگر حلقهی for شما مقدار پایانی را هم شامل شود — مانند کد زیر — چه؟
for(int i = 0; i <= 5; i++) {
IO.println(i);
}
interface IntStream اینجا هم به کمک شما میآید. متد rangeClosed دقیقاً برای همین منظور طراحی شده است.
import java.util.stream.IntStream;
...
IntStream.rangeClosed(0, 5)
.forEach(IO::println);
متد rangeClosed برای حلقه زدن از مقدار شروع تا مقدار پایانی — به صورت شامل (Inclusive) — کاربرد دارد.
چه از متد range استفاده کنید چه rangeClosed، در هر دو حالت یک Stream از مقادیر int دریافت میکنید. روی این Stream میتوانید از iterator داخلی (Internal Iterator) برای اجرای عملیات استفاده کنید. در ادامهی این مجموعه، عملیات فراتر از forEach را بررسی خواهیم کرد.
در مثالهای قبلی، iterator داخلی (Internal Iterator) بار حلقهزدن را از دوش شما برداشت. Stream مراحل پیمایش بازهی مقادیر را مدیریت میکند و مقادیر را یکییکی در اختیار شما قرار میدهد. تنها کاری که باید بکنید این است که در متد forEach روی هر عنصر تمرکز کنید، آنگاه که برایتان فراهم میشود. در مثالهای ما فقط مقدار دریافتی را چاپ کردیم. اما شما میتوانید هر عملیاتی انجام دهید — مثلاً ذخیرهی اطلاعات در پایگاه داده، ارسال به یک سرویس راهدور و غیره.
برخلاف iterator خارجی (External Iterator) که حلقهی for فراهم میکند، کدی که از iterator داخلی استفاده میکند مختصرتر است، نویز کمتری دارد، نیاز به تغییر صریح متغیر اندیس را از بین میبرد، خواندنش آسانتر است، تغییر دادنش راحتتر است و کلیاتاً تجربهی دلپذیرتری ایجاد میکند.
در کد پروژهتان دنبال حلقههای for سنتی بگردید و آنها را با استفاده از متدهای range یا rangeClosed از IntStream بازنویسی کنید. مطمئن شوید که بعد از تغییر، کد همانطور که انتظار دارید کار میکند — ترجیحاً با اجرای تستهای خودکاری که احتمالاً از قبل دارید.
هر جا یک حلقهی سادهی for دیدید، میتوانید از متد range یا rangeClosed از IntStream استفاده کنید. اگر میخواهید حلقه تا مقدار پایانی ادامه یابد اما آن مقدار شامل نشود، از متد range استفاده کنید. اگر میخواهید مقدار پایانی هم در حلقه گنجانده شود، از متد rangeClosed استفاده نمایید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/