تا الان از الگوی بسیار مفید collect(Collectors.toList()) برای جمعآوری عناصر پردازششده توسط یک استریم در List استفاده کردهاید. متد collect() یک متد پایانی است که در رابط Stream تعریف شده و شیءای از نوع Collector را به عنوان آرگومان میگیرد. این رابط Collector API خود را دارد که میتوانید برای ایجاد هر نوع ساختار حافظهای برای ذخیرهی دادههای پردازششده توسط یک استریم استفاده کنید. جمعآوری میتواند در هر نمونهای از Collection یا Map انجام شود، میتواند برای ایجاد رشتههای کاراکتر استفاده شود، و میتوانید نمونهی شخصی خود از رابط Collector را برای اضافه کردن ساختارهای خود به این فهرست ایجاد کنید.
اکثر collector هایی که استفاده خواهید کرد با یکی از متدهای factory کلاس Collectors قابل ایجاد هستند. این دقیقاً کاری است که هنگام نوشتن Collectors.toList() یا Collectors.toSet() انجام دادید. برخی collector های ساختهشده با این متدها قابل ترکیب هستند و به collector های بیشتری منجر میشوند.
API Collector در رابط
Streamو استریمهای تخصصی اعداد:IntStream،LongStreamوDoubleStreamمتفاوت عمل میکند. رابطStreamدو overload متدcollect()دارد، در حالی که استریمهای اعداد فقط یکی دارند. آن یکی دقیقاً همانی است که شیء collector به عنوان آرگومان میگیرد. پس نمیتوانید از شیء collector با استریم تخصصی اعداد استفاده کنید.
کلاس Collectors سه متد به شما میدهد تا عناصر استریم خود را در نمونهای از رابط Collection جمعآوری کنید.
toList() آنها را در شیء List جمعآوری میکند.toSet() آنها را در شیء Set جمعآوری میکند.Collection دیگری نیاز دارید، میتوانید از toCollection(supplier) استفاده کنید، که آرگومان supplier برای ایجاد شیء Collection مورد نیاز استفاده میشود. اگر به جمعآوری دادهها در نمونهای از LinkedList نیاز دارید، این متدی است که باید استفاده کنید.کد شما نباید به پیادهسازی دقیق List یا Set اتکا کند، چون بخشی از مشخصات نیست.
همچنین میتوانید پیادهسازیهای غیرقابل تغییر List و Set را با دو متد toUnmodifiableList() و toUnmodifiableSet() بدست آورید.
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
این کد از متد میانی boxed() استفاده میکند تا یک Stream<Integer> از IntStream ایجادشده توسط IntStream.range() بسازد، با boxing کردن تمام عناصر آن استریم. اجرای این کد خروجی زیر را چاپ میکند.
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
این مثال دوم یک HashSet ایجاد میکند و تکرارهای ایجادشده توسط نگاشت را حذف میکند.
No more duplicates = [0, 1, 2, 3, 4]
اجرای این کد نتیجه زیر را به شما میدهد.
No more duplicates = [0, 1, 2, 3, 4]
و این مثال آخر از یک شیء Supplier برای ایجاد نمونهی LinkedList استفاده میکند که عناصر استریم در آن جمعآوری میشوند.
linked list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
کلاس Collectors چندین متد برای ایجاد collector هایی دارد که همان کار متدهای پایانی ساده را انجام میدهند. این مورد متد factory Collectors.counting() است.
اجرای این کد نتیجه زیر را به شما میدهد.
linked list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
collector دیگر بسیار مفیدی که کلاس Collectors ارائه میدهد joining() است. این collector فقط روی استریم رشتههای کاراکتر کار میکند و عناصر آن را به یک رشتهی واحد متصل میکند.
joined = 0123456789
اجرای این کد نتیجه زیر را تولید میکند.
joined = 0123456789
میتوانید جداکنندهای به این رشته با کد زیر اضافه کنید.
joined = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
نتیجه به صورت زیر است.
joined = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
بیایید آخرین overload را در عمل ببینیم که جداکننده، پیشوند و پسوند میگیرد.
joined = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
توجه کنید این collector موارد خاص جایی که استریم شما خالی است یا فقط یک عنصر پردازش میکند را به درستی مدیریت میکند.
API Collector سه الگو برای ایجاد نقشه از عناصر استریم ارائه میدهد. اولین موردی که پوشش میدهیم نقشهای با کلیدهای boolean ایجاد میکند. با متد factory partitioningBy() ایجاد میشود.
نتیجه به صورت زیر است.
joined = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
توجه کنید این collector موارد خاص جایی که استریم شما خالی است یا فقط یک عنصر پردازش میکند را به درستی مدیریت میکند.
این collector هنگامی که نیاز به تولید این نوع رشتهی کاراکتر دارید بسیار مفید است. شاید وسوسه شوید حتی وقتی دادههای شما اصلاً در یک collection نیستند یا فقط چند عنصر دارند، از آن استفاده کنید. در چنین موردی، شاید استفاده از کلاس factory String.join() یا شیء StringJoiner به همان خوبی کار کند، بدون پرداخت هزینهی ایجاد یک استریم.
دومین collector ای که معرفی میکنیم بسیار مهم است چون به شما امکان ایجاد هیستوگرام را میدهد.
collector ای که میتوانید برای ایجاد هیستوگرام استفاده کنید با متد Collectors.groupingBy() ایجاد میشود. این collector نقشهای ایجاد میکند که برای هر عنصر استریم یک کلید با اعمال نمونهای از Function محاسبه میشود. این تابع classifier نامیده میشود.
اجرای این کد نتیجه زیر را تولید میکند.
false :: [one, two, four, five, six, nine, ten]
true :: [three, seven, eight, eleven, twelve]
این متد factory یک overload دارد که collector دیگری به عنوان آرگومان اضافی میگیرد. این collector به نام downstream collector شناخته میشود. این downstream collector ها را در پاراگراف بعدی این آموزش پوشش میدهیم، هنگامی که collector groupingBy() را معرفی میکنیم.
متد groupingBy() آرگومان دیگری نیز میپذیرد که collector دیگری است. این downstream collector نامیده میشود.
classifier استفادهشده در این مثال تابعی است که طول هر رشته را از آن استریم برمیگرداند.پس نقشه رشتهها را بر اساس طولشان در لیستها گروهبندی کرد. نوع آن Map<Integer, List<String>> است.
اجرای این کد خروجی زیر را چاپ میکند.
3 :: [one, two, six, ten]
4 :: [four, five, nine]
5 :: [three, seven, eight]
6 :: [eleven, twelve]
اجرای این کد خروجی زیر را چاپ میکند.تعداد رشتهها بر اساس طول را به شما میدهد، که همان هیستوگرام رشتهها بر اساس طول آنهاست.
3 :: 4
4 :: 3
5 :: 3
6 :: 2
سومین overload متد groupingBy() یک نمونه از Supplier به عنوان آرگومان میگیرد تا کنترل نمونهی Map را به شما بدهد.
API Collector الگوی دومی برای ایجاد نقشه ارائه میدهد: Collectors.toMap().
اجرای این کد نتیجه زیر را تولید میکند.
3 :: one, two, six, ten
4 :: four, five, nine
5 :: three, seven, eight
6 :: eleven, twelve
اگر انتظار دارید چند عنصر استریم کلید یکسان تولید کنند، میتوانید آرگومان سومی به متد toMap() بدهید.
اجرای کد قبلی نتیجه زیر را تولید میکند.
Map size = 4
Keys = [1, 2, 3, 4]
آرگومان اول: User::id تابعی است که روی عناصر استریم اعمال میشود. برای تولید یک کلید استفاده میشود.
آرگومان دوم: Function.identity() همچنین تابعی است که روی عناصر استریم اعمال میشود و برای تولید یک مقدار استفاده میشود.
از این دو عنصر، این collector یک جفت کلید-مقدار ایجاد میکند که به نقشهی نتیجه اضافه میشود.
اگر انتظار دارید چند عنصر استریم کلید یکسان تولید کنند، میتوانید آرگومان اضافی به متد toMap() بدهید. این آرگومان از نوع BinaryOperator است. پیادهسازی این آرگومان را روی عناصر متعارض هنگام شناسایی اعمال میکند. سپس عملگر دوتایی شما نتیجهای تولید میکند که به جای مقدار قبلی در نقشه قرار میگیرد.
موارد زیر نحوهی استفاده از این collector با مقادیر متعارض را نشان میدهد. مقادیر با جداکننده به هم متصل میشوند.
3 :: one, two, six, ten
4 :: four, five, nine
5 :: three, seven, eight
6 :: eleven, twelve
groupingBy() بهترین الگوی شما برای محاسبه هیستوگرام روی دادههایی است که باید تحلیل کنید.
در این مثال، سه آرگومان ارسالشده به متد toMap() به صورت زیر هستند:
element -> element.length() key mapper است.element -> element value mapper است.(element1, element2) -> element1 + ", " + element2 merge function است، که با دو عنصری که کلید یکسان تولید کردهاند فراخوانی میشود.اجرای این کد نتیجه زیر را تولید میکند.
3 :: one, two, six, ten
4 :: four, five, nine
5 :: three, seven, eight
6 :: eleven, twelve
مشابه collector groupingBy()، میتوانید supplier را به عنوان آرگومان به متد toMap() بدهید تا نمونهی Mapای که این collector استفاده میکند را کنترل کنید.
collector toMap() یک متد دوقلو به نام toConcurrentMap() دارد که دادههای شما را در یک نقشهی concurrent جمعآوری میکند. نوع دقیق نقشه توسط پیادهسازی تضمین نمیشود.
وقتی چند کلید-مقدار با بیشینهی مشترک دارید، میتوانید نقشه را معکوس کنید و از Collectors.mapping() استفاده کنید.
چاپ این هیستوگرام نتیجه زیر را تولید میکند.
3 :: 4
4 :: 3
5 :: 3
6 :: 2
میتوانید پرتکرارترین طول را با استخراج جفت کلید-مقداری که بیشترین مقدار را از این هیستوگرام دارد به دست آورید. نتیجه 3 :: 4 است. Stream API تمام ابزارهایی که برای استخراج چنین مقدار بیشینهای نیاز دارید را دارد. متأسفانه متد stream() در رابط Map وجود ندارد. پس برای ایجاد یک استریم روی نقشه، ابتدا باید یکی از collection هایی که از نقشه قابل دریافت هستند را بگیرید.
entrySet().keySet().values().اینجا هم کلید و هم مقدار بیشینه را نیاز دارید، پس انتخاب درست استریم کردن مجموعهی بازگرداندهشده توسط entrySet() است.
کدی که نیاز دارید به صورت زیر است.
Collector API collector هایی برای عملیاتهای میانی نیز دارد: mapping()، filtering() و flatMapping(). این collector ها نمیتوانند به تنهایی ایجاد شوند و همیشه به یک downstream collector نیاز دارند.
اجرای این مثال خروجی زیر را چاپ میکند.
maxValue = 3=4
میتوانید ببینید این کد از متد max() رابط Stream استفاده میکند که یک Comparator به عنوان آرگومان میگیرد. رابط Map.Entry چندین متد factory برای ایجاد چنین comparatorای دارد. آنچه در این مثال استفاده میکنیم comparatorای میسازد که میتواند نمونههای Map.Entry را مقایسه کند، با استفاده از مقدار این جفتهای کلید-مقدار. این مقایسه فقط در صورتی کار میکند که مقدار رابط Comparable را پیادهسازی کرده باشد.
این الگوی کد بسیار عمومی است و میتواند روی هر نقشهای که مقادیر comparable دارد استفاده شود. میتوانیم آن را کمتر عمومی و خواناتر کنیم، به لطف معرفی record ها در Java SE 16.
بیایید record ای برای مدلسازی جفتهای کلید-مقدار این نقشه ایجاد کنیم. ایجاد record یک خطی است. چون record های محلی در زبان مجاز هستند، میتوانید این خطوط را در هر متدی کپی کنید.
result = [TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE]
API Collector همچنین چندین عملیات پایانی ارائه میدهد:
maxBy() و minBy().summingInt()، summingLong() و summingDouble().averagingInt()، averagingLong() و averagingDouble().چاپ این هیستوگرام نتیجه زیر را به شما میدهد.
3 :: 3
4 :: 3
5 :: 3
6 :: 2
اکنون همانطور که میبینید، سه جفت کلید-مقدار برای بیشترین مقدار داریم. اگر از الگوی کد قبلی برای استخراج آن استفاده کنید، یکی از این سه انتخاب و برگردانده میشود و دو مورد دیگر پنهان میمانند.
راهحلی برای مقابله با این مشکل ایجاد نقشهای دیگر است، جایی که کلیدها تعداد رشتهها با طول مشخص هستند و مقدارها طولهایی هستند که با آن تعداد مطابقت دارند. به عبارت دیگر: باید این نقشه را معکوس کنید. این مورد استفادهی خوبی برای collector groupingBy() است. این مثال بعداً در این بخش پوشش داده میشود، چون به یک عنصر دیگر نیاز داریم تا این کد را بنویسیم.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/