عملیات flatMap میتواند برای اعتبارسنجی تبدیل عناصر استریم استفاده شود.
فرض کنید استریمی از رشتههای کاراکتر دارید که اعداد صحیح را نشان میدهند و باید آنها را با Integer.parseInt() به عدد صحیح تبدیل کنید. متأسفانه برخی از این رشتهها خراب هستند: شاید خالی، null یا دارای کاراکترهای اضافی در انتهایشان باشند. همهی اینها باعث خطا در تحلیل با NumberFormatException میشوند. البته میتوانید سعی کنید این استریم را با predicates فیلتر کنید تا رشتههای خراب حذف شوند، اما امنترین راه استفاده از الگوی try-catch است.
سعی در استفاده از filter راه درستی نیست. predicateای که مینویسید به صورت زیر خواهد بود.
Predicate<String> isANumber = s -> {
try {
int i = Integer.parseInt(s);
return true;
} catch (NumberFormatException e) {
return false;
}
};
عیب اول این است که برای دیدن اینکه کار میکند یا نه باید واقعاً تبدیل را انجام دهید. سپس باید دوباره در تابع نگاشت خود که بعداً اجرا میشود این کار را انجام دهید: این کار را نکنید!
عیب دوم این است که بازگشت از بلوک catch هرگز ایدهی خوبی نیست.
آنچه واقعاً نیاز دارید این است: وقتی عدد صحیح درستی در این رشته وجود دارد یک عدد صحیح برگردانید و اگر رشته خراب است هیچ چیزی برگردانید. این کار با یک flatmapper انجام میشود. در صورتی که رشتهی کاراکتر شما نمایانگر یک عدد صحیح باشد، استریمی با نتیجه برمیگردانید. در غیر این صورت، میتوانید یک استریم خالی برگردانید.
پس میتوانید تابع زیر را بنویسید.
اجرای این کد نتیجه زیر را تولید میکند. تمام رشتههای معیوب به آرامی حذف شدهاند.
ints = [1, 2, 3]
این استفاده از flatmap خوب کار میکند، اما overhead دارد: برای هر عنصر از استریمی که باید پردازش کنید یک استریم ایجاد میشود. از Java SE 16 به بعد، متدی به Stream API اضافه شده دقیقاً برای مدیریت این مورد. این متد mapMulti() نامیده میشود و یک BiConsumer به عنوان آرگومان میگیرد.
این BiConsumer دو آرگومان مصرف میکند:
Consumer که این BiConsumer باید نتیجهی نگاشت را با آن فراخوانی کندفراخوانی consumer با یک عنصر، آن عنصر را به استریم نتیجه اضافه میکند. در صورت عدم امکان نگاشت، biconsumer این consumer را فراخوانی نمیکند و عنصری اضافه نمیشود.
بیایید الگوی خود را با این متد mapMulti() بازنویسی کنیم.
اجرای این کد نتیجه مشابه قبلی را تولید میکند. تمام رشتههای معیوب به آرامی حذف شدهاند، اما این بار هیچ استریم دیگری ایجاد نشده.
ints = [1, 2, 3]
برای استفاده از این متد، باید نوع Consumer مورد استفاده برای اضافه کردن عناصر به استریم نتیجه را به کامپایلر بگویید.
این کار به دو روش قابل انجام است.
BiConsumer که به mapMulti() میدهید ارائه دهید. این احتمالاً سادهترین راه استفاده از این متد است.mapMulti() قرار میدهید. نحوی نیست که زیاد در کد جاوا ببینید. هم در بافت static و هم غیرstatic قابل استفاده است.اگر از مشخص کردن این نوع صرفنظر کنید، چون کامپایلر نمیتواند نوع استریم بازگشتی از فراخوانی mapMulti() را استنباط کند، آن را Stream<Object> فرض میکند.
Stream API دو متد دارد، distinct() و sorted()، که به سادگی تکرارها را تشخیص و حذف و عناصر استریم شما را مرتب میکنند. متد distinct() از متدهای hashCode() و equals() برای تشخیص تکرارها استفاده میکند. متد sorted() یک overload دارد که یک comparator میگیرد. اگر comparator ارائه ندهید، Stream API فرض میکند عناصر استریم شما comparable هستند. اگر نباشند، ClassCastException ایجاد میشود.
شاید از بخش قبلی این آموزش به یاد داشته باشید که یک استریم قرار است شیئی خالی باشد که هیچ دادهای ذخیره نمیکند. چندین استثنا برای این قانون وجود دارد و این دو متد از آنها هستند.
در واقع، برای تشخیص تکرارها، متد distinct() باید عناصر استریم شما را ذخیره کند. این کار را در یک Set داخلی انجام میدهد. همان مورد برای متد sorted() صادق است. این متد باید تمام عناصر شما را ذخیره و سپس آنها را در یک بافر داخلی مرتب کند قبل از ارسال به مرحلهی بعدی پایپلاین پردازش شما.
distinct() میتواند روی استریمهای نامتناهی (بینهایت) کار کند، sorted() نمیتواند.
Stream API دو راه برای انتخاب عناصر یک استریم به شما میدهد: بر اساس ایندکس، یا با یک predicate.
راه اول استفاده از متدهای skip() و limit() است. نکتهی مهم این است که هر بار که یک متد میانی روی استریم فراخوانی میشود، استریم جدیدی ایجاد میشود. بنابراین اگر limit() را بعد از skip() فراخوانی کنید، عناصر را روی استریم جدید بشمارید.
فرض کنید استریمی از تمام اعداد صحیح از 1 دارید. باید اعداد صحیح بین 3 و 8 را از یک استریم اعداد صحیح انتخاب کنید. شاید وسوسه شوید skip(2).limit(8) فراخوانی کنید. متأسفانه این نحوهی کار استریمها نیست. فراخوانی دوم limit(8) روی استریمی عمل میکند که از 3 شروع میشود، پس اعداد صحیح تا 11 را انتخاب میکند، که آنچه نیاز دارید نیست. کد صحیح به صورت زیر است.
این کد خروجی زیر را چاپ میکند.
result = [3, 4, 5, 6, 7]
درک این موضوع مهم است که skip(2) روی استریمی فراخوانی شده که عناصر 1, 2, 3, ... را پردازش میکند و استریم دیگری تولید میکند که عناصر 3, 4, 5, 6, ... را پردازش میکند.
پس limit(5) 5 عنصر اول آن استریم را انتخاب میکند، یعنی 3, 4, 5, 6, 7.
Java SE 9 دو متد دیگر در این حوزه معرفی کرد. به جای رد شدن و محدود کردن عناصر بر اساس ایندکس آنها در استریم، این کار را بر اساس مقدار یک predicate انجام میدهد.
dropWhile(predicate) عناصر پردازششده توسط استریم را تا زمانی که اعمال predicate روی آنها false شود حذف میکند. در آن نقطه، تمام عناصر پردازششده توسط آن استریم به استریم بعدی منتقل میشوند.takeWhile(predicate) برعکس عمل میکند: عناصر را تا زمانی که اعمال predicate روی آنها false شود به استریم بعدی منتقل میکند.توجه کنید این متدها مانند در عمل میکنند. وقتی dropWhile() در را برای عبور عناصر باز کرد، آن را نمیبندد. وقتی takeWhile() در را بست نمیتواند دوباره باز کند.
توجه کنید این متدها فقط روی استریمهای ORDERED معنادار هستند. اگر آنها را روی استریمی استفاده کنید که ORDERED نیست، استریم بعدی ممکن است هر زیرمجموعهای از عناصر upstream را دریافت کند.
Stream API الگوهای متعددی برای اتصال چند استریم به یک استریم واحد ارائه میدهد. بدیهیترین روش استفاده از متد factory concat() است.
این متد دو استریم میگیرد و استریمی با عناصر تولیدشده توسط استریم اول و به دنبال آن عناصر استریم دوم تولید میکند.
شاید بپرسید چرا این متد vararg نمیگیرد تا اتصال تعداد دلخواهی از استریمها ممکن شود.
دلیل این است که استفاده از این متد تا زمانی که دو استریم برای اتصال دارید OK است. اگر بیش از دو دارید، JavaDoc API توصیه میکند از الگوی دیگری بر اساس استفاده از flatmap استفاده کنید.
بیایید در یک مثال ببینیم چگونه کار میکند.
اجرای این کد نتیجه زیر را تولید میکند:
concat = [1, 2, 3, 4, 5, 6]
flatMap = [1, 2, 3, 4, 5, 6, 7, 8, 9]
دلیل اینکه بهتر است از روش flatMap() استفاده کنید این است که concat() در حین اتصال استریمهای واسطهای ایجاد میکند. با استفاده از Stream.concat()، استریم جدیدی برای اتصال دو استریم شما ایجاد میشود. اگر نیاز به اتصال سه استریم دارید، در نهایت یک استریم اول برای اتصال اول و دومی برای اتصال دوم ایجاد میکنید. بنابراین هر اتصال به استریمی نیاز دارد که در انتهای عملیات دور انداخته میشود.
با الگوی flatmap، فقط یک استریم واحد برای نگهداری تمام استریمهایتان ایجاد میکنید و flatmap را انجام میدهید. overhead بسیار کمتر است.
تفاوت ظریفی بین استریم تولیدشده توسط الگوی concat و flatmap وجود دارد. اگر اندازهی منبع دو استریمی که متصل میکنید مشخص باشد، اندازهی استریم نتیجه نیز مشخص است. در واقع، به سادگی مجموع دو استریم متصلشده است.
استفاده از flatmap روی یک استریم ممکن است تعداد نامعلومی عنصر برای پردازش در استریم نتیجه ایجاد کند. Stream API از تعداد عناصری که در استریم نتیجه پردازش خواهند شد ردیابی خود را از دست میدهد.
به عبارت دیگر: concat یک استریم SIZED تولید میکند، در حالی که flatmap اینطور نیست.
گاهی مفید است عناصر پردازششده توسط یک استریم را در زمان اجرا بررسی کنید. Stream API برای این کار متدی دارد: peek(). این متد برای اشکالزدایی پایپلاین پردازش داده شما در نظر گرفته شده. نباید این متد را در کد production خود استفاده کنید.
باید به شدت از استفاده از متد peek() برای انجام برخی effect های جانبی در برنامه خود خودداری کنید.
این متد یک consumer به عنوان آرگومان میگیرد که توسط API روی هر عنصر استریم فراخوانی میشود. بیایید این متد را عملی ببینیم.
اگر این کد را اجرا کنید، خروجی زیر را در کنسول خود خواهید دید.
Starting with = one
Starting with = two
Filtered = two
Mapped = TWO
Starting with = three
Filtered = three
Mapped = THREE
Starting with = four
result = [TWO, THREE]
بیایید این خروجی را تحلیل کنیم.
نکتهای که قبلاً در این آموزش دیدید و اکنون واضح است: استریم تمام عناصرش را یکییکی از ابتدا تا انتهای استریم پردازش میکند. این قبلاً ذکر شده بود و اکنون میتوانید آن را عملی ببینید.
میتوانید ببینید این الگوی peek(IO::println) برای پیگیری عناصر پردازششده توسط استریم بدون نیاز به اشکالزدایی کد بسیار مفید است. اشکالزدایی یک استریم سخت است چون باید مراقب باشید breakpoint های خود را کجا قرار دهید. اکثر اوقات، قرار دادن breakpoint ها روی پردازش استریم شما را به پیادهسازی رابط Stream هدایت میکند. این آن چیزی نیست که نیاز دارید. بیشتر اوقات باید این breakpoint ها را در کد عبارات lambda خود قرار دهید.
نتیجه مشابه قبل و به صورت زیر است.
Total population = 24493
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/