مهدی عادلی فر
بنیانگذار توسینسو و برنامه نویس

معرفی متدهای مفید در کلاس Collectors زبان جاوا | Java

کلاس Collectors در زبان برنامه نویسی جاوا|Java متدهایی برای عملیات reduction را در خود دارد. از متدهای این کلاس می توان در کار با Stream استفاده کرد. در این مطلب متدهای مهم این کلاس را معرفی می کنیم و نحوه کارکرد آنها را مشخص می کنیم.

دوره های شبکه، برنامه نویسی، مجازی سازی، امنیت، نفوذ و ... با برترین های ایران

ساخت لیست با toList

این متد نتیجه Stream را در قالب یک لیست می ریزد. نتیجه ای که این متد می دهد یک لیست جدید است و با لیست و مجموعه اصلی کاری ندارد.


List<Integer> integers = Stream.of(1, 2, 3, 4, 5, 6, 6).map(x -> x * x).collect(Collectors.toList());
    //output  [1, 4, 9, 16, 25, 36, 36]

ساخت set با متد toSet

این متد شبیه به متد toList است با این تفاوت که نتیجه را در یک set ذخیره می کند. و همانطور که می دانید set عنصر تکراری ندارد پس عناصر تکراری نتیجه حذف می شود.

Set<Integer> integers2 = Stream.of(1, 2, 3, 4, 5, 6, 6).map(x -> x * x).collect(Collectors.toSet());
    //output [16, 1, 4, 36, 9, 25]

ساخت مجموعه مشخص با متد toCollection

اگر به جز لیست و ست مجموعه دیگری مد نظر شما باشد که نتیجه stream را در آن بریزید می توانید از متد toCollection استفاده کنید. در این متد نوع مجموعه از شما خواسته می شود و داده ها در داخل آن ریخته می شود. مثلا در کد زیر ما نتیجه را در داخل یک LinkedList ریخته ایم.

LinkedList<Integer> integers=Stream.of(1, 2, 3, 4, 5, 6, 6)
                .filter(a -> a > 2)
                .collect(Collectors.toCollection(LinkedList::new));
        //output [3, 4, 5, 6, 6]

شمارش عناصر مجموعه با متد counting

این متد تعداد عناصر نتیجه را شمارش می کند و خروجی آن یک عدد long است.

long count=Stream.of(1, 2, 3, 4, 5, 6, 6).filter(s->s>4)
        .collect(Collectors.counting());
// output 3

پیدا کردن مقدار مینیمم با متد minBy

این متد مقدار کمینه یا مینیمم را از استریم برمیگرداند. در کد زیر این کد را برای داده های رشته ای و برای داده های عددی نوشته ایم. اگر می خواهید این متد را برای کلاس های دیگری به کار بگیرید باید فرایند مقایسه را در قسمت comparator پیاده سازی کرده باشید.

int integerMinimum = Stream
                .of(1, 2, 3, 4, 5, 6, 6)
                .collect(Collectors
                        .minBy(Comparator.naturalOrder()))
                .get();
        //output 1
        String stringMinimum = Stream
                .of("alpha", "beta", "gamma", "tosinso", "mehdi")
                .collect(Collectors
                        .minBy(Comparator.naturalOrder()))
                .get();
        //output alpha

پیدا کردن مقدار ماکسیمم با متد maxBy

این متد شبیه به متد minBy است با این تفاوت که مقدار بیشینه یا ماکسیمم را پیدا می کند. مانند کد زیر

int integerMinimum = Stream
                .of(1, 2, 3, 4, 5, 6, 6)
                .collect(Collectors
                        .maxBy(Comparator.naturalOrder()))
                .get();
        //output 6
        String stringMinimum = Stream
                .of("alpha", "beta", "gamma", "tosinso", "mehdi")
                .collect(Collectors
                        .maxBy(Comparator.naturalOrder()))
                .get();
        //output tosinso

قطعه بندی کردن مجموعه با استفاده از متد partitioningBy

این متد یک شرط را به عنوان ورودی دریافت می کند و با توجه به شرط مجموعه را به دو لیست تقسیم می کند که یک قسمت را در صورتی که شرط درست نباشد و قسمت دوم در صورتی که شرط درست باشد قرار می دهد. این دو لیست در قالب یک Map برگردانده می شود. پس map به وجود آمده دارای دو عنصر است که کلید اولی مقدار بولین false و دیگری مقدار true است مانند مثال زیر

final Map<Boolean, List<String>> collect = Stream
                .of("alpha", "beta", "gamma", "tosinso", "mehdi")
                .collect(Collectors
                        .partitioningBy(s -> s.length() > 4));
        //output {false=[beta], true=[alpha, gamma, tosinso, mehdi]}

در این مثال از شرط طول رشته استفاده کرده ایم که مجموعه را به دو قسمت تقسیم کرده ایم یکی اعضایی که طولشان بیشتر از 4 هست و اعضایی که طولشان بیشتر از 4 نیست.

ساخت لیست غیر قابل تغییر و فقط خواندنی با متد toUnmodifiableList

از این متد برای ساخت لیست readOnly استفاده می شود. اگر سعی کنید لیست فقط خواندنی را تغییر بدهید با استثنا UnsupportedOperationException مواجه خواهید شد.

List<String> strings = Stream
                .of("alpha", "beta", "gamma", "tosinso", "mehdi")
                .collect(Collectors.toUnmodifiableList());
         //output [alpha, beta, gamma, tosinso, mehdi]

این متد در جاوای 10 اضافه شده است پس اگر از جاوای 8 یا قبل از آن استفاده می کنید این متد برای شما ناشناخته خواهد بود. همچنین متد toUnmodifiableSet یک set فقط خواندنی را می سازد و شبه به toUnmodifiableList است.

ساخت رشته از اعضا با متد joining

در کلاس Collectors می توان با ترکیب اعضای مجموعه آنها را به رشته تبدیل کرد. در این تبدیل کردن می توان یک جدا کننده بین عناصر قرار داد و یا برای آنها پیشوند و یا پسوند قرار داد. به کد زیر دقت کنید.

List<String> strings = Arrays.asList("alpha","beta","gamma");
String collect3 = strings
     .stream()
     .distinct()
     .collect(Collectors.joining(","));
// output: alpha,beta,gamma
String collect4 = strings
     .stream()
     .map(s -> s.toString())
     .collect(Collectors.joining(",","[","]"));
// output: [alpha,beta,gamma]

در مثال اول علامت کاما(،) را برای جدا کردن اعضا قرار داده ایم. و در مثال دوم براکت را قبل و بعد از اعضا قرار داده ایم.

محاسبه میانگین از اعضای long با استفاده از متد averagingLong

این متد میانگین مقادیر مجموعه که از نوع long هستند را محاسبه می کند. دقت داشته باشید که مقدار خروجی از نوع double خواهد بود. مانند مثال زیر

List<Long> longValues = Arrays.asList(100l,200l,300l);
Double d1 = longValues
    .stream()
    .collect(Collectors.averagingLong(x -> x * 2));
// output: 400.0

همچنین متدهای averagingInt و averagingDouble نیز همین کار را برای اعضای int و double انجام می دهد. دقت کنید که خروجی همه این متدها double است.

ساخت Map‌ با استفاده از متد toMap

متد toMap یک map با استفاده از عناصر مجموعه می سازد مانند مثال زیر


List<String> strings = Arrays.asList("alpha","beta","gamma");
Map<String,Integer> map = strings
       .stream()
       .collect(Collectors
          .toMap(Function.identity(),String::length));
// output: {alpha=5, beta=4, gamma=5}

در این مثال یک map ساخته شده است که کلید های آن مقادیر لیست و طول هر رشته به عنوان مقدار آن قرار گرفته شده است.

حذف کردن عناصر تکراری هنگام ساختن map

عناصر تکراری می توانند در یک لیست وجود داشته باشند. حال اگر بخواهیم که مقادیر موجود در لیست را به عنوان کلید در نظر بگیریم مشکل تکراری بودن پیش می آید. در متد toMap می توان این مشکل را به راحتی حل کرد که کلید های یکتا داشته باشیم. مثل کد زیر

List<String> strings = Arrays.asList("alpha","beta","gamma","beta");
Map<String,Integer> map = strings
        .stream()
        .collect(Collectors
          .toMap(Function.identity(),String::length,(i1,i2) -> i1));
// output: {alpha=5, gamma=5, beta=4}

دقت داشته باشید که Function.identity یک اشاره گر است که به عنصر لیست اشاره می کند و i1, i2 دو عنصری هستند که تکراری هستند. در کد بالا به خاطر این که فقط یک مقدار داشته باشیم ما i1 را انتخاب کرده ایم که شما می توانید هرکدام را که مایل بودید انتخاب کنید.

به دست آوردن مجموع عناصر با استفاده از متد summingInt

اگر مجموعه شما شامل اعداد صحیح باشد با متد summingInt می توانید مجموع آنها را حساب کنید. مانند مثال زیر

List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
Integer sum = integers
    .stream()
    .collect(Collectors.summingInt(x -> x));
// output: 27

دقت داشته باشید که در کد بالا خود عنصر را به متد summingInt داده ایم ولی می توانید از عملیات مختلفی برای این متد استفاده کنید مانند مثال زیر که مجموعه شامل عناصر رشته ای است ولی ما به متد طول آنها را که int است داده ایم.

List<String> strings = Arrays.asList("alpha","beta","gamma");
Integer collect4 = strings
      .stream()
      .collect(Collectors.summingInt(String::length));
// output: 18

متدهای summingDouble, summingLong هم مشابه به summingInt هستند با این تفاوت که نوع داده ورودی آنها باید مناسب آنها باشد.

انجام عمل summarize کردن

اگر بخواهید در قالب یک شی از کلاس IntSummaryStatistics عملیات ریاضی و آماری اصلی را برای یک مجموعه به دست آورید باید از متد summarizingInt استفاده کنید. این متد مقادیری مانند میانگین و مینیمم و ماکسیمم و تعداد اعضا و جمع آنها را به شما خواهد داد مانند کد زیر

List<Integer> integers = Arrays.asList(1,2,3,4,5,6,6);
IntSummaryStatistics stats = integers
          .stream()
          .collect(Collectors.summarizingInt(x -> x ));
//output: IntSummaryStatistics{count=7, sum=27, min=1, average=3.857143, max=6}

دقت کنید که با استفاده از کلاس IntSummaryStatistics می توانیم هرکدام از مقادیر را که بخواهیم به شکل زیر دریافت کنیم

stats.getAverage();   // 3.857143
stats.getMax();       // 6
stats.getMin();       // 1
stats.getCount();     // 7
stats.getSum();       // 27

گروه بندی کردن اعضا

بسیاری از اوقات پیش می آید که می خواهید عناصر را گروه بندی کنید اگر با زبان SQL آشنا باشید متوجه هستید که دستور group by چه کاربردهایی دارد. با استفاده از کلاس Collectors و متد groupingBy می توان عمل گروه بندی را انجام داد. به مثال زیر دقت کنید

List<String> strings = Arrays.asList("alpha","beta","gamma");
Map<Integer, List<String>> collect = strings
          .stream()
          .collect(Collectors.groupingBy(String::length));
// output: {4=[beta], 5=[alpha, gamma]}

در مجموعه بالا عناصر را بر اساس طول رشته گروه بندی کرده ایم که خروجی یک map است که کلید آن طول رشته و مقدار آن یک لیست از رشته هایی است که طولشان برابر با عدد کلید است. حال شما می توانید به جای طول رشته منطق مورد نظر خود را برای گروه بندی پیاده سازی کنید. همچنین می توانیم نوع خروجی مجموعه را به شکل زیر تعیین کنیم که در کد زیر خروجی به شکل یک LinkedList آورده شده است.

List<String> strings = Arrays.asList("alpha","beta","gamma");
Map<Integer, LinkedList<String>> collect1 = strings
            .stream()
            .collect(Collectors.groupingBy(String::length, 
                Collectors.toCollection(LinkedList::new)));
// output: {4=[beta], 5=[alpha, gamma]}

با وب سایت tosinso همراه باشید


مهدی عادلی فر
مهدی عادلی فر

بنیانگذار توسینسو و برنامه نویس

مهدی عادلی، بنیان گذار TOSINSO. کارشناس ارشد نرم افزار کامپیوتر از دانشگاه صنعتی امیرکبیر و #C و جاوا و اندروید کار می کنم. در زمینه های موبایل و وب و ویندوز فعالیت دارم و به طراحی نرم افزار و اصول مهندسی نرم افزار علاقه مندم.

نظرات