همانطور که قبلاً اشاره کردیم، کلاس Collectors فقط استریمهای اشیاء را پوشش میدهد چون متد collect() که شیء collector به عنوان آرگومان میگیرد فقط در Stream وجود دارد. اگر نیاز به جمعآوری استریم اعداد دارید، باید عناصر سازندهی collector را بشناسید.
به زبان ساده، collector از چهار جزء اصلی ساخته شده. دو اولی برای جمعآوری عناصر استریم استفاده میشوند. سومی فقط برای استریمهای موازی نیاز است. چهارمی برای برخی انواع collector ها نیاز است که پردازش نهایی روی container ساختهشده نیاز دارند.
اولین جزء برای ایجاد container ای است که عناصر استریم در آن جمعآوری میشوند. مثلاً ArrayList، HashSet یا HashMap. ایجاد چنین container توسط نمونهای از Supplier مدلسازی میشود. این اولین جزء supplier نامیده میشود.
دومین جزء اضافهی یک عنصر از استریم به این container را مدلسازی میکند. این عملیات توسط پیادهسازی Stream API بارها فراخوانی میشود تا تمام عناصر استریم یکییکی به container اضافه شوند.
در API Collector، این جزء توسط نمونهای از BiConsumer مدلسازی میشود. این biconsumer دو آرگومان میگیرد.
این biconsumer در بافت API Collector accumulator نامیده میشود.
این دو جزء باید برای کار collector کافی باشند، اما Stream API محدودیتی میآورد که دو جزء دیگر را نیز ضروری میکند.
شاید به یاد داشته باشید Stream API از موازیسازی پشتیبانی میکند. موازیسازی عناصر استریم شما را به substream هایی تقسیم میکند که هر کدام توسط هستهی CPU پردازش میشوند. API Collector میتواند در چنین بافتی کار کند: هر substream در نمونهی جداگانهای از container ایجادشده توسط collector شما جمعآوری میشود.
وقتی این substream ها پردازش شدند، چندین container دارید که هر کدام عناصر substream پردازششده را دارند. این container ها یکسان هستند چون با supplier یکسان ایجاد شدهاند. حالا به راهی برای ادغام آنها در یکی نیاز دارید. API Collector به یک سومین جزء نیاز دارد، combiner، که این container ها را با هم ادغام میکند.
دو راه برای ایجاد Collector خود دارید:
Collector.of(supplier, biconsumer, binaryoperator, characteristics)Collector.of(supplier, biconsumer, binaryoperator, function, characteristics) که finisher اضافی نیز میگیرد.سومین جزء combiner است. توسط نمونهای از BinaryOperator مدلسازی میشود که دو container نیمهپر میگیرد و یکی برمیگرداند.
چهارمین جزء در overload دوم finisher نامیده میشود که بعداً پوشش داده خواهد شد.
پارامتر آخر characteristics collector نامیده میشود که vararg از Collector.Characteristics است.
همچنین میتوانید Stream را با ارسال این عناصر به متد Stream.collect(supplier, accumulator, combiner) جمعآوری کنید. در این مورد نیازی به ایجاد collector نیست.
با سه جزء اول، میتوانید از متد collect() از استریمهای تخصصی اعداد استفاده کنید.
collect = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
اجرای این کد نتیجه زیر را تولید میکند.
collect = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
جمعآوری این داده در یک Set فقط نیاز به تغییر پیادهسازی supplier و تنظیم انواع متناسب با آن دارد.
کدی که در پاراگراف قبل نوشتید تقریباً آنچه نیاز دارید انجام میدهد: رشتههای کاراکتر را در نمونهای از StringBuffer متصل میکند. اما Collectors.joining() مستقیماً String تولید میکند. پس چطور انجام میشود؟
API Collector چهارمین جزء برای این مورد تعریف میکند که finisher نامیده میشود. finisher نمونهای از Function است که container را تبدیل میکند.
Function<StringBuffer, String> finisher = stringBuffer -> stringBuffer.toString();
متدهای collectingAndThen() یک collector و یک finisher میگیرد. این امکان ترکیب collector ها و ساخت collector های پیچیدهتر را فراهم میکند.
اجرای این کد نتیجه زیر را تولید میکند.
collect = 0123456789
متد Collectors.teeing() در Java SE 12 اضافه شد و دو downstream collector و یک تابع ادغام میگیرد. دادهها در یک نوبت پردازش میشوند.
بیایید یک مورد استفاده را بررسی کنیم. فرض کنید Car و Truck record دارید.
کد قبلی خروجی زیر را چاپ میکند.
maxValue = 3=4
در مرحلهی اول، هیستوگرامی از نوع Map<Integer, Long> ساختید و در مرحلهی دوم، مقدار بیشینهی این هیستوگرام را با مقایسهی جفتهای کلید-مقدار بر اساس مقدار استخراج کردید.
این مرحلهی دوم در واقع تبدیل یک نقشه به جفت کلید/مقدار خاصی از آن نقشه است. میتوانید آن را با تابع زیر مدلسازی کنید.
Function<Map<Integer, Long>, Map.Entry<Integer, Long>> finisher =
map -> map.entrySet().stream()
.max(Map.Entry.comparingByValue())
.orElseThrow();
نوع این تابع ممکن است در نگاه اول پیچیده به نظر برسد. در واقع، فقط یک جفت کلید-مقدار را از یک نقشه استخراج میکند. پس نمونهای از Map با نوع مشخصی میگیرد و یک جفت کلید-مقدار از آن نقشه برمیگرداند که نمونهای از Map.Entry با همان نوع است.
اکنون که این تابع را دارید، میتوانید مرحلهی استخراج مقدار بیشینه را در خود collector با استفاده از collectingAndThen() ادغام کنید. الگو به صورت زیر در میآید.
Electric vehicles = [Car[color=BLUE, engine=ELECTRIC, drive=WD2, passengers=4]]
اجرای کد قبلی خروجی زیر را چاپ میکند.
maxValue = 3=4
ممکن است بپرسید چرا باید این کد که نسبتاً پیچیده به نظر میرسد را بنویسید؟
اکنون که یک استخراجکنندهی مقدار بیشینه مدلشده توسط یک collector واحد دارید، میتوانید آن را به عنوان downstream collector برای collector دیگری استفاده کنید. توانایی انجام این کار امکان ترکیب collector های بیشتر برای اجرای محاسبات پیچیدهتر روی دادههای شما را فراهم میکند.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/