در مقالهی قبلی این مجموعه آموزشی، نحوهی تبدیل حلقههای سادهی نوشتهشده به سبک دستوری (Imperative) به سبک تابعی (Functional) را بررسی کردیم. در این مقاله میبینیم چگونه حلقههای کمی پیچیدهتر را بازنویسی کنیم — حلقههایی که مجبوریم در آنها با گامهای مشخص روی بعضی مقادیر در یک بازه حرکت کنیم.
وقتی روی یک بازهی مقادیر، یکییکی حلقه میزنیم، متد range() از IntStream به کار پیادهسازی در سبک تابعی میآید. این متد یک Stream برمیگرداند که مقادیر درون بازهی مشخصشده را یکییکی تولید میکند. در نگاه اول، برای رد کردن بعضی مقادیر ممکن است وسوسه شویم از متد filter() روی Stream استفاده کنیم. اما راهحل سادهتری وجود دارد: متد iterate() از IntStream.
در اینجا حلقهای را میبینیم که از گام (Step) برای رد شدن از بعضی مقادیر در بازهی مورد نظر استفاده میکند:
for(int i = 0; i < 15; i = i + 3) {
IO.println(i);
}
مقدار متغیر اندیس i از 0 شروع میشود و با پیشرفت حلقه به اندازهی 3 افزایش مییابد. وقتی با چنین حلقهای مواجه شدید که حلقهزدن روی هر مقدار در بازه نیست و بعضی مقادیر از حلقه رد میشوند و پرش میکنند، استفاده از متد iterate() از IntStream را در نظر بگیرید.
قبل از بازنویسی کد، بیایید نگاه دقیقتری به حلقهی for() در کد قبلی بیندازیم — اما با یک عینک خیالی که امکانات استفاده از Lambda را به ما نشان میدهد:
//imaginary code
for(int i = 0; i < 15; i = i + 3) //imperative
for(seed, i -> i < 15, i -> i + 3) //functional
آرگومان اولی که به حلقهی for پاس داده میشود، مقدار شروع یا بذر (Seed) برای حلقه است و میتواند بدون تغییر باقی بماند. آرگومان دوم یک Predicate است که میگوید مقدار متغیر اندیس i نباید از مقدار 15 بیشتر شود. در سبک تابعی این را با یک IntPredicate جایگزین میکنیم. آرگومان سوم مقدار متغیر اندیس را افزایش میدهد و در سبک تابعی، این یک IntUnaryOperator است. interface IntStream یک متد static به نام iterate() دارد که کد خیالی بالا را بهخوبی بازنمایی میکند: iterate(int seed, IntPredicate hasNext, IntUnaryOperator next).
بیایید حلقه را با استفاده از سبک تابعی بازنویسی کنیم:
import java.util.stream.IntStream;
...
IntStream.iterate(0, i -> i < 15, i -> i + 3)
.forEach(IO::println);
کار بسیار سادهای بود: علامتهای ; به , تبدیل شدند و ما از دو Lambda استفاده کردیم: یکی برای IntPredicate و دیگری برای IntUnaryOperator.
علاوه بر حلقهزدن با گامهای مشخص روی مقادیر، اغلب با حلقههای نامحدود (Unbounded Loop) هم سر و کار داریم. این حلقهها کمی پیچیدگی بیشتری به کار اضافه میکنند، اما هیچ چیزی نیست که APIهای تابعی جاوا از پس آن برنیایند — همانطور که در ادامه خواهیم دید.
بیایید حلقهی سبک دستوری زیر را ببینیم که علاوه بر داشتن گام، نامحدود است و از دستور break استفاده میکند:
for(int i = 0;; i = i + 3) {
if(i > 20) {
break;
}
IO.println(i);
}
شرط پایان i < 15 دیگر وجود ندارد و حلقه با ;; تکراری نامحدود شده است. اما در داخل حلقه، دستور break وجود دارد که اگر مقدار i بیشتر از 20 شد، از حلقه خارج شویم.
برای سبک تابعی، میتوانیم آرگومان دوم — یعنی IntPredicate — را از فراخوانی متد iterate() حذف کنیم، اما این کار حلقه را به یک Stream نامحدود (Infinite Stream) تبدیل میکند. معادل تابعی دستور break سبک دستوری، متد takeWhile() است. این متد iterator داخلی — یعنی Stream — را متوقف میکند اگر IntPredicateای که به آن پاس داده شده false برگرداند. بیایید حلقهی نامحدود for با break قبلی را به سبک تابعی تبدیل کنیم:
IntStream.iterate(0, i -> i + 3)
.takeWhile(i -> i <= 20)
.forEach(IO::println);
متد iterate() Overloaded است و در دو نسخه موجود است: یکی با IntPredicate و دیگری بدون آن. از نسخهی بدون Predicate استفاده کردیم تا یک Stream نامحدود بسازیم که مقادیر را از بذر یا همان مقدار شروع تولید میکند. IntUnaryOperator که بهعنوان آرگومان دوم پاس داده شده، گامها را تعیین میکند. بنابراین در کد مثال دادهشده، Stream مقادیر 0، 3، 6 و الی آخر را تولید میکند. از آنجایی که میخواهیم حلقه را طوری محدود کنیم که اندیس از مقدار 20 بیشتر نشود، از takeWhile() استفاده میکنیم. Predicateای که به takeWhile() پاس داده شده میگوید حلقه تا زمانی میتواند ادامه یابد که مقدار پارامتر دادهشده — یعنی اندیس i — از مقدار 20 بیشتر نشود.
در مقالهی قبلی دیدیم که range() و rangeClosed() جایگزین مستقیم حلقهی سادهی for هستند. اگر حلقه کمی پیچیدهتر شد، نگران نباشید — جاوا اینجا هم پوشش لازم را فراهم کرده است. میتوانید از متد iterate() از IntStream استفاده کنید و در صورتی که حلقه با break خاتمه مییابد، بهصورت اختیاری از takeWhile() هم استفاده نمایید.
هر جا حلقهی for با گام (Step) دیدید، از متد iterate() با سه آرگومان استفاده کنید: یک بذر یا مقدار شروع، یک IntPredicate برای شرط پایان، و یک IntUnaryOperator برای گامها. اگر حلقهی شما از دستور break استفاده میکند، آرگومان IntPredicate را از فراخوانی متد iterate() حذف کنید و بهجای آن از متد takeWhile() استفاده نمایید. متد takeWhile() معادل تابعی break در سبک دستوری است.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/