تا الان در این آموزش، استریمهای زیادی ساختهاید و همهی آنها با فراخوانی متد stream() از رابط Collection ایجاد شدهاند. این متد بسیار راحت است: ایجاد استریم با این روش فقط به دو خط کد ساده نیاز دارد و میتوانید از این استریم برای آزمایش تقریباً هر ویژگی از Stream API استفاده کنید.
همانطور که خواهید دید، راههای بسیار دیگری برای ایجاد استریم روی اشیاء مختلف وجود دارد. دانستن این راهها به شما امکان میدهد از Stream API در بخشهای مختلف برنامهی خود استفاده کنید و کد خواناتر و قابل نگهداریتری بنویسید.
بیایید پیش از ورود به جزئیات هر کدام، مروری سریع بر روشهایی که در این آموزش خواهید دید داشته باشیم.
دسته اول از الگوها از متدهای factory رابط Stream استفاده میکنند. با آنها میتوانید استریم را از عناصر زیر بسازید:
حتی میتوانید یک استریم خالی ایجاد کنید که در برخی شرایط میتواند مفید باشد.
دیدید که میتوانید روی یک collection استریم بسازید. اگر فقط یک iterator و نه یک collection کامل دارید، الگوی مناسب شما وجود دارد: میتوانید روی یک iterator استریم بسازید. اگر یک آرایه دارید، الگوی دیگری برای ساخت استریم از عناصر آرایه نیز وجود دارد.
اما اینها تنها گزینهها نیستند. الگوهای بسیاری برای اشیاء شناختهشدهی JDK نیز اضافه شدهاند. میتوانید استریم را از عناصر زیر بسازید:
همچنین میتوانید با الگوی Builder استریم بسازید.
میدانید که متد stream() در رابط Collection موجود است. این احتمالاً کلاسیکترین روش ایجاد استریم است.
در برخی موارد ممکن است بخواهید روی محتوای یک map استریم بسازید. رابط Map متد stream() ندارد، بنابراین نمیتوانید مستقیماً چنین استریمی ایجاد کنید. اما میتوانید از طریق سه collection به محتوای map دسترسی پیدا کنید:
keySet()entrySet()values().الگوی صحیح این است که یکی از این collection ها را بگیرید و روی آن استریم بسازید.
Stream API الگویی برای ایجاد استریم از یک iterator ساده به شما میدهد. یک iterator شیء بسیار سادهای برای ساختن است، بنابراین میتواند راه بسیار مناسبی برای ایجاد استریم روی یک منبع دادهی غیراستاندارد باشد. الگو به صورت زیر است.
اجرای کد قبلی نتیجه زیر را تولید میکند.
ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
این الگو چند عنصر جادویی دارد که بعداً در این آموزش پوشش داده میشوند. بیایید مروری سریع بر آنها داشته باشیم.
estimateSize تعداد عناصری است که فکر میکنید این استریم مصرف خواهد کرد. مواردی وجود دارد که این اطلاعات به سادگی قابل دریافت است: مثلاً اگر روی یک آرایه یا collection استریم میسازید. اما مواردی هم هست که این اطلاعات نامعلوم است.
پارامتر characteristics بعداً در این آموزش پوشش داده میشود. برای بهینهسازی پردازش دادههای شما استفاده میشود.
آرگومان parallel به API میگوید آیا استریمی که میخواهید بسازید یک استریم موازی است یا نه. استریمهای موازی نیز بعداً پوشش داده میشوند.
بیایید با سادهترین الگو شروع کنیم: ایجاد یک استریم خالی. رابط Stream یک متد factory برای این کار دارد. میتوانید به صورت زیر استفاده کنید.
اجرای این کد خروجی زیر را نشان میدهد.
strings = []
مواردی وجود دارد که ایجاد یک استریم خالی میتواند بسیار مفید باشد. در واقع، در بخش قبلی این آموزش یکی دیدید. الگویی که دیدید از استریمهای خالی و flatmap برای حذف عناصر نامعتبر از یک استریم استفاده میکند. از Java SE 16 به بعد، این الگو با الگوی mapMulti() جایگزین شده است.
این دو الگو بسیار شبیه به هم هستند. الگوی اول از متد factory of() در رابط Stream استفاده میکند. الگوی دوم از متد factory stream() کلاس Arrays استفاده میکند. و در واقع، اگر کد Stream.of() را بررسی کنید، خواهید دید که Arrays.stream() را فراخوانی میکند.
الگوی اول به صورت زیر عمل میکند.
اجرای این مثال اول خروجی زیر را به شما میدهد:
ints = [1, 2, 3]
و این دومی است.
اجرای این مثال دوم خروجی زیر را به شما میدهد:
strings = [one, two, three]
در رابط Stream دو متد factory برای این کار وجود دارد.
متد اول generate() است که یک supplier به عنوان آرگومان میگیرد. هر بار که عنصر جدیدی نیاز باشد، این supplier فراخوانی میشود.
میتوانید چنین استریمی با کد زیر بسازید، اما این کار را نکنید!
اجرای مثال قبلی نتیجه زیر را تولید میکند.
strings = [+, +, +, +, +]
اگر این کد را بدون limit() اجرا کنید، خواهید دید که هرگز متوقف نمیشود. اگر این کار را انجام دهید و صبور باشید، شاید یک OutOfMemoryError ببینید. اگر نباشید، بهتر است برنامه را از طریق IDE خود متوقف کنید. این استریم عناصر تولید میکند و هرگز متوقف نمیشود. واقعاً یک استریم نامتناهی تولید میکند.
هنوز این نکته را پوشش ندادهایم، اما داشتن چنین استریمهایی کاملاً قانونی است! شاید بپرسید چه استفادهای میتوان از آنها کرد؟ در واقع استفادههای زیادی دارد. برای استفاده از آنها، باید در نقطهای این استریم را قطع کنید و Stream API راههای مختلفی برای این کار به شما میدهد. اضافه کردن یک عملیات میانی limit() یکی از آنهاست، و راههای بیشتری در راه است.
متد limit() یک متد short-circuiting نامیده میشود: میتواند مصرف عناصر یک استریم را متوقف کند. شاید به یاد داشته باشید که در استریم دادهها یک عنصر در یک زمان پردازش میشوند: هر عنصر از تمام عملیاتهای تعریفشده در استریم شما عبور میکند، از اولین تا آخرین. به همین دلیل عملیات limit میتواند تولید عناصر بیشتر را متوقف کند.
استفاده از supplier عالی است اگر به تولید استریمهای ثابت نیاز دارید. اگر به استریم نامتناهی با مقادیر متفاوت نیاز دارید، میتوانید از الگوی iterate() استفاده کنید.
این الگو با یک seed کار میکند، که اولین عنصر تولیدی است. سپس از یک UnaryOperator برای تولید عنصر بعدی استریم با تبدیل عنصر قبلی استفاده میکند.
نتیجه زیر را باید ببینید.
+
++
+++
++++
+++++
هنگام استفاده از این الگو، فراموش نکنید تعداد عناصر پردازششده توسط استریم خود را محدود کنید.
از Java SE 9 به بعد، این الگو یک overload دارد که یک predicate به عنوان آرگومان میگیرد. متد iterate() وقتی این predicate false شود، تولید عناصر را متوقف میکند. کد قبلی میتواند به صورت زیر از این الگو استفاده کند.
اجرای این کد نتیجهی مشابه مثال قبلی را به شما میدهد.
ایجاد یک بازه اعداد با الگوی قبلی آسان است. اما با استریمهای تخصصی اعداد و متدهای factory range() آنها حتی آسانتر است.
متد range() مقدار اولیه و حد بالای بازه را میگیرد، که حد بالا محاسبه نمیشود. میتوانید حد بالا را با متد rangeClosed() نیز شامل کنید. فراخوانی LongStream.range(0L, 10L) به سادگی استریمی از long های بین 0 تا 9 تولید میکند.
این متد range() همچنین میتواند برای تکرار روی عناصر یک آرایه استفاده شود. نحوهی انجام آن به صورت زیر است.
نتیجه به صورت زیر است.
listLetters = [A, B, C, D, A, B, C, D, A, B]
کارهای زیادی بر اساس این الگو میتوانید انجام دهید. توجه داشته باشید که چون IntStream.range() یک IntStream ایجاد میکند، باید از متد mapToObj() برای نگاشت آن به استریمی از اشیاء استفاده کنید.
کلاس Random برای ایجاد سریهای تصادفی اعداد استفاده میشود. از Java SE 8 به بعد، چندین متد به این کلاس اضافه شده تا استریمهایی از اعداد تصادفی با انواع مختلف: int، long و double ایجاد کند.
میتوانید یک نمونه از Random با ارائهی یک seed بسازید. این seed یک پارامتر long است. اعداد تصادفی به آن seed بستگی دارند. برای یک seed مشخص، همیشه توالی یکسانی از اعداد دریافت خواهید کرد. این میتواند در بسیاری از شرایط مفید باشد، از جمله نوشتن تستها. در آن صورت میتوانید روی توالیای از اعداد که از پیش شناختهشده است تکیه کنید.
سه متد برای تولید چنین استریمی وجود دارد، همه در کلاس Random تعریف شدهاند: ints()، longs() و doubles().
چندین overload برای همهی این متدها موجود است که آرگومانهای زیر را میپذیرند:
الگوی کد اول که 10 عدد صحیح تصادفی بین 1 تا 4 تولید میکند به صورت زیر است.
اگر از همان seed مثال استفاده کنید، خروجی کنسول شما به صورت زیر خواهد بود.
randomInts = [3, 3, 3, 2, 1, 2, 2, 3, 1, 3]
توجه کنید از متد boxed() که در استریم تخصصی اعداد موجود است استفاده کردیم که به سادگی این استریم را به استریم معادل wrapper type نگاشت میکند. بنابراین IntStream توسط این متد به Stream<Integer> نگاشت میشود.
الگوی دوم که استریمی از boolean های تصادفی تولید میکند به صورت زیر است. هر عنصر آن استریم با احتمال 80% برابر true است.
اگر از همان seed استفاده کنید، نتیجه زیر را خواهید دید.
numberOfTrue = 773
میتوانید این الگو را برای تولید هر نوع شیء با احتمال مورد نیاز خود تطبیق دهید. مثال دیگری که استریمی با حروف A، B، C و D تولید میکند به صورت زیر است. احتمال هر حرف به صورت زیر است:
با همان seed، نتیجه زیر را دریافت خواهید کرد.
A :: 470
B :: 303
C :: 117
D :: 110
ساختن نقشه با این groupingBy() ممکن است در این مرحله برای شما نامفهوم به نظر برسد. نگران نباشید؛ این الگو بعداً در این آموزش پوشش داده خواهد شد.
کلاس String در Java SE 8 متد chars() را دریافت کرد. این متد یک IntStream برمیگرداند که کاراکترهای آن رشته را به شما میدهد.
هر کاراکتر به صورت یک code point، عدد صحیحی که شاید شما را به یاد کدهای ASCII بیندازد، داده میشود. در برخی موارد ممکن است بخواهید این عدد صحیح را به رشتهای تبدیل کنید که فقط همان کاراکتر را داشته باشد.
بسته به نسخهی JDK که استفاده میکنید، دو الگو برای این کار دارید.
تا Java SE 10 میتوانید از کد زیر استفاده کنید.
اجرای کد قبلی نتیجه زیر را به شما میدهد.
letters = [H, e, l, l, o, , D, u, k, e]
متد factory toString() در Java SE 11 به کلاس Character اضافه شد که میتوانید برای سادهتر کردن این کد از آن استفاده کنید.
این کد خروجی مشابهی تولید میکند.
letters = [H, e, l, l, o, , D, u, k, e]
توانایی باز کردن یک استریم روی یک فایل متنی یک الگوی بسیار قدرتمند است.
API ورودی-خروجی جاوا الگویی برای خواندن یک خط از یک فایل متنی دارد: BufferedReader.readLine(). میتوانید این متد را از داخل یک حلقه فراخوانی کنید و کل فایل متنی خود را خطبهخط برای پردازش بخوانید.
توانایی پردازش این خطوط با Stream API کد خواناتر و قابل نگهداریتری به شما میدهد.
الگوهای متعددی برای ایجاد چنین استریمی وجود دارد.
اگر نیاز به بازسازی کد موجود بر اساس استفاده از یک buffered reader دارید، میتوانید از متد lines() تعریفشده روی این شیء استفاده کنید. اگر کد جدیدی برای پردازش محتوای فایل متنی مینویسید، میتوانید از متد factory Files.lines() استفاده کنید. این متد آخر یک Path به عنوان آرگومان میگیرد و یک overload نیز دارد که یک Charset میگیرد، در صورتی که فایلی که میخوانید با UTF-8 کدگذاری نشده باشد.
شاید بدانید که یک منبع فایل، مانند هر منبع I/O دیگری، وقتی دیگر به آن نیاز ندارید باید بسته شود. از آنجا که از این منبع فایل از طریق Stream API استفاده میکنید، شاید بپرسید چگونه این را مدیریت خواهید کرد.
خبر خوب این است که رابط Stream از رابط BaseStream ارثبری میکند که خودش از رابط AutoCloseable ارثبری میکند. استریم خودش یک منبع است که در صورت نیاز میتوانید آن را ببندید. این واقعاً در تمام مثالهای حافظهای که دیدید لازم نبود، اما هنگام کار با منابع I/O اجباری میشود.
مثالی که تعداد هشدارها (warnings) را در یک فایل گزارش میشمارد به صورت زیر است. توجه داشته باشید به دلیل محدودیتهای Playground، نمیتوانید این کد را در مرورگر خود اجرا کنید. برای اجرای آن، باید کپی کنید، در IDE خود جایگذاری کنید و مسیر را متناسب با نصب محلی خود تنظیم کنید.
الگوی try-with-resources متد close() استریم شما را فراخوانی میکند، که به نوبهی خود فایل متنی تحلیلشده را به درستی میبندد.
آخرین مثال از این مجموعه الگوها، متدی است که به کلاس Pattern اضافه شده تا استریمی از عناصر تولیدشده از اعمال یک عبارت باقاعده روی یک رشتهی کاراکتر ایجاد کند.
فرض کنید نیاز دارید یک رشته را با یک جداکنندهی مشخص جدا کنید. دو الگو برای این کار دارید.
String.split() را فراخوانی کنید؛Pattern.compile().split() استفاده کنید.هر دو الگو یک آرایه از رشتهها به شما میدهند، شامل عناصر نتیجهی جداسازی.
الگوی ایجاد استریم از این آرایه را دیدید. بیایید این کد را بنویسیم.
اجرای کد قبلی خروجی زیر را چاپ میکند.
Words = [For, there, is, good, news, yet, to, hear, and, fine, things, to, be, seen]
کلاس Pattern همچنین متدی برای شما دارد. کاری که میتوانید انجام دهید فراخوانی Pattern.compile().splitAsStream() است. مثال کدی که میتوانید با این متد بنویسید به صورت زیر است.
اجرای این کد نتیجه مشابهی تولید میکند.
Words = [For, there, is, good, news, yet, to, hear, and, fine, things, to, be, seen]
شاید بپرسید کدام الگو بهتر است. برای پاسخ به این سؤال، باید نگاه دقیقی به الگوی اول بیندازید. ابتدا یک آرایه برای ذخیرهی نتیجهی جداسازی ایجاد میکنید، سپس روی این آرایه استریم میسازید.
در الگوی دوم هیچ آرایهای ایجاد نمیشود، بنابراین overhead کمتری دارد.
قبلاً دیدید که برخی استریمها میتوانند از عملیاتهای short-circuit استفاده کنند (دربارهی این نکته بعداً بیشتر خواهیم دید). اگر چنین استریمی دارید، جداسازی کل رشته و ایجاد آرایهی نتیجه میتواند overhead مهم اما بیفایدهای باشد. مشخص نیست پایپلاین استریم شما تمام عناصرش را برای تولید نتیجه مصرف کند.
حتی اگر استریم شما نیاز به مصرف تمام عناصر برای تولید نتیجه داشته باشد، ذخیرهی تمام این عناصر در یک آرایه همچنان overhead حافظهای است که نیازی به پرداخت آن نیست.
بنابراین در هر دو مورد، استفاده از الگوی splitAsStream() بهتر است. از نظر حافظه بهینهتر است و در برخی موارد از نظر CPU هم بهتر عمل میکند.
ایجاد استریم با این الگو یک فرآیند دو مرحلهای است. ابتدا عناصری که استریم مصرف خواهد کرد را به builder اضافه میکنید. سپس استریم را از این builder میسازید. وقتی builder شما برای ایجاد استریم استفاده شد، نمیتوانید عناصر بیشتری به آن اضافه کنید و نه میتوانید دوباره از آن برای ساختن استریم دیگری استفاده کنید. اگر این کار را انجام دهید، یک IllegalStateException دریافت خواهید کرد.
الگو به صورت زیر است.
اجرای این کد خروجی زیر را چاپ میکند.
list = [one, two, three, four]
آخرین الگویی که در این آموزش پوشش میدهیم دربارهی تحلیل بدنهی یک پاسخ HTTP است. دیدید که میتوانید روی خطوط یک فایل متنی استریم بسازید، همین کار را میتوانید روی بدنهی یک پاسخ HTTP انجام دهید. این الگو توسط HTTP Client API که به JDK 11 اضافه شده ارائه میشود.
نحوهی کار به صورت زیر است. از آن روی متنی که به صورت آنلاین در دسترس است استفاده خواهیم کرد: The Tale of Two Cities اثر Charles Dickens که توسط پروژهی Gutenberg در اینجا در دسترس است: https://www.gutenberg.org/files/98/98-0.txt
ابتدای فایل متنی اطلاعاتی دربارهی خود متن به شما میدهد. کتاب از خطی که شامل «A TALE OF TWO CITIES» است شروع میشود. انتهای فایل مجوزی است که این فایل تحت آن توزیع شده.
فقط به متن کتاب نیاز داریم و میخواهیم header و footer این فایل توزیعی را حذف کنیم. توجه داشته باشید نمیتوانید این کد را در مرورگر خود اجرا کنید. باید کپی کنید و در IDE خود جایگذاری کنید.
اجرای این کد خروجی زیر را چاپ میکند.
# lines = 15902
استریم توسط body handler ایجاد میشود که به عنوان آرگومان متد send() ارائه میدهید. HTTP Client API چندین body handler به شما میدهد. آنچه برای مصرف بدنه به صورت استریم نیاز دارید، آنی است که توسط متد factory HttpResponse.BodyHandlers.ofLines() ایجاد میشود. این روش مصرف بدنه پاسخ بسیار از نظر حافظه کارآمد است. اگر استریم خود را به دقت بنویسید، بدنه پاسخ هرگز در حافظه ذخیره نمیشود.
تصمیم گرفتیم تمام خطوط متن را در یک لیست قرار دهیم، اما بسته به پردازشی که نیاز دارید روی این داده انجام دهید، لزوماً نیازی به این کار ندارید. در واقع، در اکثر موارد احتمالاً ذخیرهی این داده در حافظه ایدهی بدی است.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/