Skip to content

5. نوع Stream<T> در جاوا ۸

5.1. مثال-۰۱ – کلاس Stream

عملیات روی استریم‌های Observable شباهت‌های زیادی با استریم‌های Stream دارد. یک تفاوت این است که یک عنصر از یک جریان Stream را نمی‌توان پردازش کرد تا زمانی که کل جریان Stream دریافت نشده باشد، در حالی که یک عنصر از یک جریان Observable را می‌توان پردازش کرد (مشاهده شده) به محض دریافت آن، بدون انتظار برای دریافت کل جریان Observable. تفاوت دیگر این است که، به محض دریافت Stream، مقادیر آن با کشیدن تک‌تک آن‌ها از Stream مورد استفاده قرار می‌گیرند. برای قابل مشاهده، این فرآیند متفاوت است. به محض اینکه مقداری را صادر می‌کند، آن مقدار به مشترک آن ارسال می‌شود.

چندین کلاس مفهوم Stream را پیاده‌سازی می‌کنند. در اینجا کلاس Stream<T> را معرفی می‌کنیم:

Image

کلاس Stream شامل ۳۹ متد است. در اینجا به چند مورد از آن‌ها می‌پردازیم. کد زیر را در نظر بگیرید:

  

package dvp.java8.streams;

import java.util.List;

import dvp.data.Personne;
import dvp.data.Personnes;

public class Exemple01 {
    public static void main(String[] args) {
        // فهرست افراد
        List<Personne> personnes = Personnes.get();
        // نمایش ۱
        personnes.stream().forEach(p -> {
            System.out.println(p);
        });
        System.out.println("----------------");
        // نمایش ۲
        personnes.stream().forEach(System.out::println);
    }
}
  • خط ۱۱: یک لیست از افراد ایجاد می‌شود؛
  • خط ۱۳: از این لیست، یک Stream ایجاد می‌شود. بنابراین می‌توان تمام مجموعه‌ها را به جریان‌های Stream تبدیل کرد. این امکان را به شما می‌دهد تا از تمام متدهای این کلاس استفاده کنید، که به شما اجازه می‌دهد عناصر مجموعه را به شیوه‌ای مختصرتر از حلقه ها پردازش کنید. همچنین به شما امکان می‌دهد تا در صورت امکان از پردازش موازی عناصر بهره‌مند شوید؛
  • خط ۱۳: متد [Stream.forEach] دارای امضای زیر است:
 

می‌توانیم ببینیم که پارامتر این متد، رابط کاربردی [Consumer<T>] است که در بخش 4.4 معرفی شده است – رابطی که تنها متد آن از نوع T استفاده می‌کند و هیچ چیزی را بازنمی‌گرداند.

  • در کد:

        personnes.stream().forEach(p -> {
            System.out.println(p);
});
  • [personnes.stream()] یک جریان از عناصر با نوع [Personne] تولید می‌کند که متد [forEach] را تغذیه می‌کند. پارامتر p از نوع [Personne] است و تابع لامبدا ارائه‌شده این شخص را نمایش می‌دهد؛

کد بالا را می‌توان به شکل زیر ساده‌سازی کرد (خط ۱۸):


personnes.stream().forEach(System.out::println);

به جای ارسال مقدار یک تابع لامبدا به عنوان پارامتر، ما ارجاع به یک متد موجود را ارسال می‌کنیم، در این مورد متد println از کلاس System.out. البته، این متد باید امضای صحیحی داشته باشد، که در این مورد امضای متد [Consumer.accept] است: void accept(T t). همانطور که قبلاً ذکر شد، پارامتر متد [accept] از نوع [Personne] خواهد بود؛

نتایج زیر را به‌دست می‌آوریم:

1
2
3
4
5
6
7
{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}
{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}
{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}
----------------
{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}
{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}
{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}

پس از پردازش یک Stream، دیگر نمی‌توان از آن استفاده کرد. اگر بخواهید آن را دوباره پردازش کنید، باید بازسازی شود. این موضوع با کد زیر [Exemple01b] نشان داده شده است:


package dvp.java8.streams;

import java.util.stream.Stream;

import dvp.data.Personne;
import dvp.data.Personnes;

public class Exemple01b {
    public static void main(String[] args) {
        //جریان شخص
        Stream<Personne> personnes = Personnes.get().stream();
        // نمایش ۱
        personnes.forEach(p -> {
            System.out.println(p);
        });
        System.out.println("----------------");
        // نمایش ۲
        personnes.forEach(System.out::println);
    }
}
  • خط ۱۱: برای بهینه‌سازی کد، تصمیم می‌گیریم Stream را تنها یک بار بسازیم. نتایج به‌دست‌آمده سپس به شرح زیر است:

{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}
{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}
{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}
----------------
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(Unknown Source)
    at java.util.stream.ReferencePipeline$Head.forEach(Unknown Source)
at dvp.java8.streams.Exemple02b.main(Exemple02b.java:18)

هر بار که بخواهیم از Stream استفاده کنیم، باید آن را تولید کنیم، حتی اگر قبلاً تولید شده باشد.

5.2. مثال ۰۲ – پردازش موازی عناصر در یک جریان

  

کد زیر را در نظر بگیرید:


package dvp.java8.streams;

import java.util.List;

import dvp.data.Personne;
import dvp.data.Personnes;

public class Exemple02 {
    public static void main(String[] args) {
        // فهرست افراد
        List<Personne> personnes = Personnes.get();
        //نمایش ۱
        personnes.stream().forEach(Exemple02::affiche);
        System.out.println("-----------------");
        // نمایش ۲
        personnes.stream().parallel().forEach(Exemple02::affiche);
    }

    public static void affiche(Personne p) {
        System.out.printf("Personne %s sur thread %s%n", p, Thread.currentThread().getName());
    }
}
  • خطوط ۱۹–۲۱: متد [affiche] رشته jSON را برای یک شخص، به همراه نام نخ اجرایی (execution thread) که نمایش در آن در حال انجام است، در کنسول می‌نویسد؛
  • خط ۱۳: فهرستی از افراد را نمایش می‌دهد. توجه داشته باشید که پارامتر متد [forEach]، مرجع به متد استاتیک قبلی است؛
  • خط ۱۶: همین فرآیند انجام می‌شود، اما با استفاده از متد [parallel]، که به سیستم دستور می‌دهد عناصر جریان را به صورت موازی در چندین نخ پردازش کند. همه پردازش‌ها را نمی‌توان به صورت موازی انجام داد. در اینجا باید فرض کنیم که ترتیب نمایش بی‌اهمیت است، زیرا در پردازش موازی هیچ تضمینی در مورد ترتیبی که نخ‌ها اجرا خواهند شد وجود ندارد. همچنین شایان ذکر است که یک نحو وجود دارد که برای هر دو متد Stream و Observable فراگیر خواهد شد:
flux.m1(e1->...).m2(e2->..).m3(e3->...)...
  • (ادامه)
    • flux عناصر e1 را تولید می‌کند که به روش m1 وارد می‌شوند؛
    • flux.m1 به نوبه خود جریانی از عناصر e2 است که به روش m2 تغذیه می‌شوند؛
    • flux.m1.m2 یک جریان از عناصر e3 است که به روش m3 تغذیه می‌کند؛

نوع عناصر e1، e2 و e3 ممکن است در حین پردازش جریان اولیه تغییر کند.

اجرای این کد نتیجه زیر را تولید می‌کند:

1
2
3
4
5
6
7
Personne {"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"} sur thread main
Personne {"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"} sur thread main
Personne {"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"} sur thread main
-----------------
Personne {"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"} sur thread main
Personne {"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"} sur thread ForkJoinPool.commonPool-worker-1
Personne {"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"} sur thread ForkJoinPool.commonPool-worker-2

می‌توانیم ببینیم که اجرای موازی (خطوط ۵–۷) در سه نخ (thread) مختلف انجام شد و ترتیب عناصر را همانطور که در خطوط ۱–۳ نشان داده شده است، دنبال نکرد. در این سند، ما تأکید کمی بر پردازش موازی عناصر در داخل Stream خواهیم داشت، زیرا این امر مستلزم بحث در مورد شرایطی است که چنین پردازشی را ممکن می‌سازد. سپس درمی‌یابیم که تنها تعداد کمی از عملیات را می‌توان به‌صورت موازی انجام داد. یکی از عملیات که به‌طور طبیعی برای موازی‌سازی مناسب است، جمع عناصر عددی یک جریان است که اکنون آن را ارائه خواهیم کرد.

5.3. مثال-۰۳ – پردازش موازی عناصر یک جریان

  

کد زیر را در نظر بگیرید (مثال ۰۳الف):


package dvp.java8.streams;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Exemple03a {
    public static void main(String[] args) {

        final long limite = 10_000_000L;
        //تعداد پردازنده‌ها
        System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
        // فهرست اعداد
        long début = new Date().getTime();
        List<Long> nombres = new ArrayList<>();
        for (long i = 0; i < limite; i++) {
            nombres.add(i);
        }
        System.out.printf("création de la liste des %s nombres en %s ms%n", limite, new Date().getTime() - début);
        // جمع اعداد – روش متوالی
        début = new Date().getTime();
        long somme = nombres.stream().reduce(0L, (s, i) -> s + i);
        System.out.printf("somme séquentielle : somme=%s, durée (ms)=%s%n", somme, new Date().getTime() - début);
    }
}
  • در خط ۲۲، ما از متد [reduce] استفاده می‌کنیم که امضای زیر را دارد:
  • متد [reduce] با عناصر از نوع T کار می‌کند؛
  • روش [reduce] پردازش یکسانی را روی تمام عناصر یک جریان اعمال می‌کند: مقدار اولیه یک انباشتگر به عنوان پارامتر اول ارائه می‌شود. یک متد که رابط تابعی [BinaryOperator] [2] را پیاده‌سازی می‌کند، به‌عنوان پارامتر دوم ارائه می‌شود: این متد بر اساس هر عنصر و انباشتگر، مقدار جدیدی برای انباشتگر بازمی‌گرداند. مقدار نهایی انباشتگر، مقداری است که توسط متد [reduce] بازگردانده می‌شود. کد [3] این مکانیزم را نشان می‌دهد. متد [apply] متد رابط عملکردی [BinaryOperator] [2] است؛

بیایید به کد مثال بازگردیم:

  • خط ۱۲: تعداد هسته‌های شناسایی‌شده توسط JVM نمایش داده می‌شود؛
  • خطوط ۱۵–۱۸: یک لیست از ۱۰ میلیون عدد ایجاد می‌شود؛
  • خط ۲۲: مجموع این اعداد به‌صورت متوالی با استفاده از یک نخ واحد محاسبه می‌شود؛

نتایج زیر به دست می‌آیند:

1
2
3
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000000 nombres en 4336 ms
somme séquentielle : somme=49999995000000, durée (ms)=225

اکنون، بیایید خط ۲۲ کد را با موارد زیر جایگزین کنیم (Example03b):


long somme = nombres.stream().parallel().reduce(0L, (s, i) -> s + i);

ما به سیستم دستور می‌دهیم که عناصر جریان را به‌صورت موازی با استفاده از چندین نخ پردازش کند. این امر ممکن است زیرا ترتیب افزودن اعداد اهمیتی ندارد. بنابراین می‌توانیم n1 عدد را به یک رشته T1، n2 عدد را به یک رشته T2، ... و در نهایت مجموع‌های ارائه‌شده توسط این رشته‌های مختلف را با هم جمع کنیم. سپس نتایج زیر را به دست می‌آوریم:

1
2
3
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000000 nombres en 4332 ms
somme parallèle : somme=49999995000000, durée (ms)=184

بنابراین عملاً هیچ بهبود عملکردی وجود ندارد. در مثال‌های بعدی، این وضعیت اغلب صادق خواهد بود. خودِ مدیریت نخ‌ها وقت‌گیر است. عملیاتی که توسط هر هسته انجام می‌شود باید به اندازه کافی پیچیده باشد تا بهبود عملکرد قابل توجه باشد. این موضوع با مثال زیر (مثال 03c) نشان داده می‌شود:


package dvp.java8.streams;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.BinaryOperator;

public class Exemple03c {
    public static void main(String[] args) {

        final long limite = 10_000L;
        // تعداد پردازنده‌ها
        System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
        // فهرست اعداد
        long début = new Date().getTime();
        List<Long> nombres = new ArrayList<>();
        for (long i = 0; i < limite; i++) {
            nombres.add(i);
        }
        System.out.printf("création de la liste des %s nombres en %s ms%n", limite, new Date().getTime() - début);
        // جمع اعداد – روش متوالی
        début = new Date().getTime();
        BinaryOperator<Long> bo = (s, i) -> {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
            }
            return s + i;
        };
        long somme = nombres.stream().reduce(0L, bo);
        System.out.printf("somme séquentielle : somme=%s, durée (ms)=%s%n", somme, new Date().getTime() - début);
    }
}
  • خط ۳۰: متد [reduce] دوباره استفاده می‌شود، و ارجاع به متد در خطوط ۲۳–۲۹ به عنوان یک پارامتر به آن پاس داده می‌شود؛
  • خط ۲۸: متد [bo] مجموع دو پارامتر خود را برمی‌گرداند؛
  • خطوط ۲۴–۲۷: به صورت مصنوعی، رشته ۱ برای شبیه‌سازی پردازش سنگین، به مدت ۱ میلی‌ثانیه در انتظار گذاشته می‌شود؛

این امر سپس نتایج زیر را به دست می‌دهد:

1
2
3
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000 nombres en 2 ms
somme séquentielle : somme=49995000, durée (ms)=13617

حال، اگر خط ۳۰ را با عبارت زیر جایگزین کنیم:


long somme = nombres.stream().parallel().reduce(0L, bo);

نتایج زیر را به دست می‌آوریم:

1
2
3
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000 nombres en 2 ms
somme séquentielle : somme=49995000, durée (ms)=1598

می‌توانیم به‌وضوح افزایش عملکرد حاصل از محاسبه موازی جمع را ببینیم. هنگام پردازش ۸ عدد:

  • رشتهٔ ترتیبی به مدت ۸ بار ۱ میلی‌ثانیه، یعنی ۸ میلی‌ثانیه، منتظر می‌ماند؛
  • ۸ نخ موازی هر کدام به طور همزمان ۱ میلی‌ثانیه منتظر می‌مانند (برای سادگی)، بنابراین در مجموع ۱ میلی‌ثانیه برای ۸ عدد زمان صرف می‌شود؛

بنابراین می‌توان انتظار داشت که اجرای موازی ۸ برابر سریع‌تر از اجرای ترتیبی باشد. در اینجا تقریباً همین‌طور است.

5.4. مثال-۰۴ – فیلتر کردن یک جریان

  

کد زیر را در نظر بگیرید:


package dvp.java8.streams;

import java.util.List;

import dvp.data.Personne;
import dvp.data.Personnes;

public class Exemple04 {
    public static void main(String[] args) {
        // فهرست افراد
        List<Personne> personnes = Personnes.get();
        // دیدها
        System.out.println("age < 28 ----------------------");
        personnes.stream().filter(p -> p.getAge() < 28).forEach(p -> {
            System.out.println(p);
        });
        System.out.println("poids < 50 ----------------------");
        personnes.stream().filter(p -> p.getPoids() < 50).forEach(p -> {
            System.out.println(p);
        });
        System.out.println("age < 28 ----------------------");
        personnes.stream().filter(p -> p.getAge() < 28).forEach(System.out::println);
        System.out.println("poids < 50 ----------------------");
        personnes.stream().filter(p -> p.getPoids() < 50).forEach(System.out::println);
    }
}
  • خط ۱۴: متد [Stream.filter] دارای امضای زیر است:
 
  • متد [filter] به عنوان پارامتر، یک نمونه از رابط تابعی [Predicate] که در بخش 4.2 ارائه شده است را انتظار دارد، تنها متدی که باید در آن پیاده‌سازی شود به صورت زیر است: boolean test(T t);
  • متد [filter] عناصر استریم را که شرط Predicate را برآورده می‌کنند، بازمی‌گرداند. بنابراین برای فیلتر کردن Stream استفاده می‌شود؛

کد زیر را در نظر بگیرید:


package dvp.java8.streams;

import java.util.List;

import dvp.data.Personne;
import dvp.data.Personnes;

public class Exemple04 {
    public static void main(String[] args) {
        // فهرست افراد
        List<Personne> personnes = Personnes.get();
        // دیدها
        System.out.println("age < 28 ----------------------");
        personnes.stream().filter(p -> p.getAge() < 28).forEach(p -> {
            System.out.println(p);
        });
        System.out.println("poids < 50 ----------------------");
        personnes.stream().filter(p -> p.getPoids() < 50).forEach(p -> {
            System.out.println(p);
        });
        System.out.println("age < 28 ----------------------");
        personnes.stream().filter(p -> p.getAge() < 28).forEach(System.out::println);
        System.out.println("poids < 50 ----------------------");
        personnes.stream().filter(p -> p.getPoids() < 50).forEach(System.out::println);
    }
}
  • خطوط ۱۴–۱۶: نمایش افرادی که سنشان کمتر از ۲۸ است؛
  • خطوط ۱۸–۲۰: نمایش افرادی که وزنشان کمتر از ۵۰ است؛
  • خط ۲۲: همان کار خطوط ۱۴–۱۶ را انجام می‌دهد اما به صورت مختصرتر؛
  • خط ۲۴: همان کاری را که خطوط ۱۸–۲۰ انجام می‌دهند انجام می‌دهد، اما مختصرتر؛

نتایج اجرای برنامه به شرح زیر است:

age < 28 ----------------------
{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}
{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}
poids < 50 ----------------------
{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}
age < 28 ----------------------
{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}
{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}
poids < 50 ----------------------
{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}

5.5. مثال-05 – ایجاد یک Stream<T2> از یک Stream<T1>

  

کد زیر را در نظر بگیرید:


package dvp.java8.streams;

import java.util.List;

import dvp.data.Personne;
import dvp.data.Personnes;

public class Exemple05 {
  public static void main(String[] args) {
    // فهرست افراد
    List<Personne> personnes = Personnes.get();
    // مشاهده‌ها
    System.out.println("Personne --> String ----------------------");
    personnes.stream().map(p -> p.getNom()).forEach(System.out::println);
    System.out.println("Personne --> Integer ----------------------");
    personnes.stream().map(p -> p.getAge()).forEach(System.out::println);
  }
}
  • در خط ۱۳، متد [Stream.map] دارای امضای زیر است:
 

پارامتر متد [Stream.map] یک نمونه از رابط تابعی [Function] است که در بخش 4.3 ارائه شده و تنها متد آن که باید پیاده‌سازی شود عبارت است از: R apply(T t). می‌توانیم ببینیم که، با داشتن یک نوع T، تابع [apply] یک نوع R تولید می‌کند. بنابراین، متد [Stream.map] یک استریم Stream از نوع R را از یک استریم از نوع T تولید می‌کند (یک استریم از نوع T در اینجا به معنای، با یک عدم دقت فنی که آن را حفظ خواهیم کرد، یک استریم از عناصر نوع T است).

اکنون کد را در مثال بررسی کنیم:

  • خط ۱۴: برای یک شخص p، تنها نام حفظ می‌شود. بنابراین ما یک استریم از نوع String به دست می‌آوریم؛
  • خط ۱۴: برای یک شخص p، تنها نام حفظ می‌شود. بنابراین ما یک جریان از Integer به دست می‌آوریم؛

نتایج به‌دست‌آمده به شرح زیر است:

1
2
3
4
5
6
7
8
Personne --> String ----------------------
jean
marie
camille
Personne --> Integer ----------------------
20
10
30

5.6. مثال-۰۶ – سایر متدهای کلاس Stream<T>

  

ما برخی از ۳۹ متد کلاس Stream را با کد زیر نشان می‌دهیم:


package dvp.java8.streams;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dvp.data.Personne;
import dvp.data.Personnes;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Exemple06 {

    // نقشه‌ساز jSON
    static private ObjectMapper jsonMapper = new ObjectMapper();

    public static void main(String[] args) throws JsonProcessingException {
        // فهرست افراد
        List<Personne> personnes = Personnes.get();
        // تمام افراد
        affiche("all", personnes);
        // اولین شخص
        affiche("findFirst", personnes.stream().findFirst().get());
        // هر شخص
        affiche("findAny", personnes.stream().findAny().get());
        // افراد به جز نفر اول
        affiche("skip 1", personnes.stream().skip(1L).collect(Collectors.toList()));
        // دو نفر اول
        affiche("limit 2", personnes.stream().limit(2L).collect(Collectors.toList()));
        // تعداد افراد
        affiche("count", personnes.stream().count());
        // مسن‌ترین فرد
        affiche("age max", personnes.stream().max(Comparator.comparingInt(Personne::getAge)).get());
        // سبک‌ترین فرد
        affiche("poids min", personnes.stream().min(Comparator.comparingDouble(Personne::getPoids)).get());
        //آخرین نفر بر اساس ترتیب الفبایی نام خانوادگی
        affiche("nom max", personnes.stream().max((p1, p2) -> p1.getNom().compareToIgnoreCase(p2.getNom())).get());
        // جمع سن همه افراد
        affiche("âge total (reduce)", personnes.stream().map(p -> p.getAge()).reduce(0, (a1, a2) -> a1 + a2));
        //افراد به ترتیب صعودی سن
        affiche("personnes par âge croissant",
                personnes.stream().sorted(Comparator.comparingInt(Personne::getAge)).collect(Collectors.toList()));
        //آیا افراد بالای ۱۰۰ سال وجود دارند؟
        affiche("des personnes de + de 100 ans (anyMatch)", personnes.stream().anyMatch(p -> p.getAge() > 100));
        //آیا همه افراد ۱۰۰ ساله یا کمتر هستند؟
        affiche("des personnes de + de 100 ans (noneMatch)", personnes.stream().noneMatch(p -> p.getAge() > 100));
        // آیا همه افراد بالای ۸ سال هستند؟
        affiche("des personnes de + de 8 ans (allMatch)", personnes.stream().allMatch(p -> p.getAge() > 8));
        //افراد بر اساس جنسیت گروه‌بندی شده‌اند
        affiche("personnes regroupées par sexe", personnes.stream().collect(Collectors.groupingBy(p -> p.getSexe())));
        //حذف عناصر تکراری از یک لیست
        affiche("distinct", Stream.of(1, 2, 1).distinct().collect(Collectors.toList()));
        //از یک `Stream<Stream<T>>`، یک `Stream<T>` ایجاد می‌کنیم
        affiche("flatMap", Stream.of(1, 2, 3).flatMap(i -> Stream.of(i, i + 10)).collect(Collectors.toList()));
        //از یک `Stream<Stream<Integer>>`، یک `IntStream` ایجاد می‌کنیم و مجموع آن را محاسبه می‌کنیم
        affiche("flatMapToInt", Stream.of(1, 2, 3).flatMapToInt(i -> IntStream.of(i, i + 10)).sum());
        //از یک Stream<Stream<Integer>>، یک DoubleStream ایجاد می‌کنیم و سپس یک آرایه
        affiche("flatMapToDouble", Stream.of(1, 2, 3).flatMapToDouble(i -> DoubleStream.of(i, i * 1.2)).toArray());
        //حداکثر یک جریان اعداد صحیح
        affiche("reduce Integer::max", Stream.of(1, 10, 8).reduce(Integer::max).get());
        //حداقل یک جریان از دوبل‌ها
        affiche("reduce Integer::min", Stream.of(1.5, 10.4, 8.9).reduce(Double::min).get());
        //میانگین یک جریان اعداد صحیح
        affiche("IntStream average", IntStream.of(1, 10, 8).average().getAsDouble());
        //آمار برای یک جریان اعداد صحیح
        affiche("IntStream summaryStatistics", IntStream.of(1, 10, 8).summaryStatistics());
    }

    public static <T> void affiche(String message, T value) throws JsonProcessingException {
        System.out.println(String.format("%s ----", message));
        System.out.println(jsonMapper.writeValueAsString(value));
    }
}
  • خطوط ۷۲ و ۷۵: رشته jSON را از پارامتر دوم متد نمایش می‌دهند؛
  • خط ۲۴: رشته jSON را برای همه افراد نمایش می‌دهد. نتیجه زیر به دست می‌آید:
all ----
[{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"},{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"},{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}]

5.6.1. [findFirst]


// شخص اول
affiche("findFirst", personnes.stream().findFirst().get());

متد [findFirst] اولین عنصر یک جریان را در صورت وجود برمی‌گرداند. امضای آن به شرح زیر است:

نتیجه از نوع **Optional&lt;T&gt;** است، نوعی که در جاوا ۸ معرفی شده است:

کلاس Optional&lt;T&gt; به نشانگرهای null اجازه می‌دهد تا به شیوه‌ای متفاوت مدیریت شوند. متدی که انتظار می‌رود نوعی T را بازگرداند، که ممکن است مقدار null را داشته باشد، می‌تواند انتخاب کند که نوعی Optional&lt;T&gt;** را بازگرداند. متد [Optional<T>.isPresent()] به شما امکان می‌دهد تشخیص دهید که آیا متد مقداری برگردانده است یا خیر. کد زیر [Exemple06b] بخشی از نحوه کار Optional<T>** را نشان می‌دهد:


package dvp.java8.streams;

import java.util.Optional;

import com.fasterxml.jackson.core.JsonProcessingException;

public class Exemple06b {

    public static void main(String[] args) throws JsonProcessingException {
        // اختیاری بدون مقدار
        Optional<Integer> o1 = m1();
        System.out.println(o1.isPresent());
        affiche(o1);
        // اختیاری با مقدار
        Optional<Integer> o2 = m2();
        System.out.println(o2.isPresent());
        affiche(o2);
    }

    private static void affiche(Optional<Integer> o1) {
        try {
            // مقدار پارامتر اختیاری را بازیابی می‌کند
            // در صورت عدم وجود مقدار، یک استثنا پرتاب می‌کند
            System.out.println(o1.get());
        } catch (Throwable th) {
            System.out.printf("%s : %s%n", th.getClass().getName(), th.getMessage());
        }

    }

    public static Optional<Integer> m1() {
        // بدون مقدار
        return Optional.empty();
    }

    public static Optional<Integer> m2() {
        // یک مقدار
        return Optional.of(10);
    }
}

نتایج به‌دست‌آمده به شرح زیر است:


false
java.util.NoSuchElementException : No value present
true
10

بیایید به کد نشان‌دهنده متد [findFirst] بازگردیم:


// اولین شخص
affiche("findFirst", personnes.stream().findFirst().get());
  • خط ۲: برای ساده‌سازی کد، ما از متد [get] روی Optional<Personne> که توسط متد [findFirst] تولید شده است، استفاده می‌کنیم. یک رویهٔ کدنویسی خوب ایجاب می‌کند که قبل از فراخوانی متد [get]، متد [Optional<Personne>.isPresent()] را فراخوانی کنیم؛

نتیجهٔ به‌دست‌آمده به شرح زیر است:

findFirst ----
{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}

5.6.2. [findAny]


// هر شخص
affiche("findAny", personnes.stream().findAny().get());

متد [findAny] دارای امضای زیر است:

 

متد [findAny] می‌تواند هر عنصری را از جریان بازگرداند. در حین آزمایش مشاهده شد که اجرای ترتیبی، اولین عنصر جریان را بازمی‌گرداند، در حالی که اجرای موازی می‌تواند در واقع هر عنصری را بازگرداند. این موضوع توسط کد زیر، [Exemple06c]، نشان داده شده است:


package dvp.java8.streams;

import java.util.List;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import dvp.data.Personne;
import dvp.data.Personnes;

public class Exemple06c {

    // نقشه‌ساز jSON
    static private ObjectMapper jsonMapper = new ObjectMapper();

    public static void main(String[] args) throws JsonProcessingException {
        // فهرست افراد
        List<Personne> personnes = Personnes.get();
        // تمام افراد
        affiche("all", personnes);
        // هر شخص
        affiche("findAny parallèle", personnes.stream().parallel().findAny().get());
        // هر شخص
        affiche("findAny séquentiel", personnes.stream().findAny().get());
    }

    public static <T> void affiche(String message, T value) throws JsonProcessingException {
        System.out.println(String.format("%s ----", message));
        System.out.println(jsonMapper.writeValueAsString(value));
    }
}
  • خط ۲۲: findAny به‌صورت موازی اجرا شد؛
  • خط ۲۴: findAny به‌صورت متوالی اجرا شد؛

نتایج به‌دست‌آمده به شرح زیر است:

1
2
3
4
5
6
all ----
[{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"},{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"},{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}]
findAny parallèle ----
{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}
findAny séquentiel ----
{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}
  • خط ۴: اجرای موازی آیتم ۲ را از لیست افراد بازگرداند. می‌توانست مورد دیگری باشد؛
  • خط ۶: اجرای متوالی اولین عنصر از لیست افراد را بازگرداند؛

استفاده از روش [findAny] تنها در پردازش موازی یک جریان منطقی به نظر می‌رسد.

5.6.3. [skip]


// افراد به جز نفر اول
affiche("skip 1", personnes.stream().skip(1L).collect(Collectors.toList()));

متد [skip] دارای امضای زیر است:

 

متد [skip] n عنصر اول یک استریم را رد می‌کند. همانطور که در مستندات بالا اشاره شده است، اجرای این متد به صورت موازی افزایش عملکرد کمی به همراه دارد و حتی ممکن است منجر به کاهش عملکرد شود. این امر به این دلیل است که برای رد کردن n عنصر اول، نخ‌ها مجبور به هماهنگی می‌شوند، که این امر مزایای عملکردی حاصل از موازی‌سازی را خنثی می‌کند.

متد [skip] یک استریم Stream<Personne> را بازمی‌گرداند که توسط متد [collect] به نوع List<Personne> تبدیل می‌شود، که امضای آن به شرح زیر است:

 

متد [collect] یک نمونه از نوع [Collector] را به عنوان پارامتر می‌پذیرد که امضای پیچیده‌ای دارد. پیاده‌سازی‌های از پیش تعریف‌شده‌ای برای نوع [Collector] وجود دارد، که معمولاً به این معنی است که شما نیازی به پیاده‌سازی آن به صورت دستی ندارید. در اینجا، پیاده‌سازی مورد استفاده [Collectors.toList()] است. [Collectors] کلاسی است که شامل متدهای استاتیک متعددی است که نوع [Collector<T,A,R>] را پیاده‌سازی می‌کنند. این اولین جایی است که باید به آن مراجعه کنید وقتی می‌خواهید یک Stream را به یک مجموعه استاندارد جاوا تبدیل کنید:

 

ما بعداً از برخی از این متدها استفاده خواهیم کرد.

اجرای کد نتیجه زیر را تولید می‌کند:

skip 1 ----
[{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"},{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}]

عنصر اول در لیست (jean) حذف شده است.

5.6.4. [limit]


// دو نفر اول
affiche("limit 2", personnes.stream().limit(2L).collect(Collectors.toList()));

متد [limit] دارای امضای زیر است:

 

متد [limit] به شما امکان می‌دهد تنها n عنصر اول یک جریان را حفظ کنید. این متد برای پردازش موازی مناسب نیست.

اجرا نتیجه زیر را تولید می‌کند:

limit 2 ----
[{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"},{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}]

5.6.5. [count]


//تعداد افراد
affiche("count", personnes.stream().count());

متد [count] دارای امضای زیر است:

 

متد [count] تعداد عناصر در یک Stream را برمی‌گرداند. اجرای موازی این متد هیچ‌گونه بهبود عملکردی به همراه ندارد، همان‌طور که در کد زیر (Example06d1) نشان داده شده است:


package dvp.java8.streams;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;

public class Exemple06d1 {
    public static void main(String[] args) {

        final long limite = 10_000_000L;
        // تعداد پردازنده‌ها
        System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
        // فهرست اعداد
        long début = new Date().getTime();
        List<Long> nombres = new ArrayList<>();
        for (long i = 0; i < limite; i++) {
            nombres.add(i);
        }
        System.out.printf("création de la liste des %s nombres en %s ms%n", limite, new Date().getTime() - début);
        // شمارش اعداد – روش ترتیبی
        Stream<Long> sNombres = nombres.stream();
        début = new Date().getTime();
        long count = sNombres.count();
        System.out.printf("comptage séquentiel : compteur=%s, durée (ms)=%s%n", count, new Date().getTime() - début);
    }
}
  • خطوط ۱۱–۲۲: یک Stream حاوی ۱۰ میلیون عدد ایجاد می‌شود؛
  • خطوط 22–24: شمارش Stream؛

اجرا نتیجه زیر را تولید می‌کند:

1
2
3
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000000 nombres en 4407 ms
comptage séquentiel : compteur=10000000, durée (ms)=67

اگر خط ۲۲ کد با عبارت زیر جایگزین شود (Example06d2):


Stream<Long> sNombres = nombres.stream().parallel();

نتایج زیر به دست می‌آیند:

1
2
3
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000000 nombres en 4341 ms
comptage parallèle : compteur=10000000, durée (ms)=100

5.6.6. [max, min]


// مسن‌ترین فرد
affiche("age max", personnes.stream().max(Comparator.comparingInt(Personne::getAge)).get());

روش [max] دارای امضای زیر است:

 

متد [max] با استفاده از مقایسه‌کننده‌ای که به‌عنوان پارامتر به آن داده می‌شود، حداکثر مقدار یک جریان را برمی‌گرداند. Comparator یک رابط تابعی است که تنها متد قابل پیاده‌سازی آن امضای زیر را دارد: int compare (T o1, T o2). این متد باید در صورتی که o1 < o2 باشد مقدار -1، در صورتی که o1.equals(o2) باشد مقدار 0، و در صورتی که o1 > o2 باشد مقدار +1 را بازگرداند. رابط تابعی Comparator دارای متدهای استاتیک پیش‌فرض متعددی است که رابط Comparator را برای رایج‌ترین موارد پیاده‌سازی می‌کنند. بنابراین، در عبارت:


affiche("age max", personnes.stream().max(Comparator.comparingInt(Personne::getAge)).get());

ما از متد استاتیک [Comparator.comparingInt] استفاده می‌کنیم که امضای زیر را دارد:

 

نوع ToIntFunction یک رابط تابعی است:

 

متد [applyAsInt] از رابط تابعی ToIntFunction یک نوع int را از یک نوع T تولید می‌کند. بیایید به کد خود بازگردیم:


affiche("age max", personnes.stream().max(Comparator.comparingInt(Personne::getAge)).get());

پارامتر واقعی برای متد [Comparator.comparingInt] باید یک لامبدا از نوع Personne --> int باشد. ما ارجاع به متد [Personne.getAge] را پاس می‌کنیم، که در واقع این امضا را دارد. در نهایت، ما شخص با بالاترین سن را به دست خواهیم آورد. ما یک نوع Optional<Personne> به دست می‌آوریم و با استفاده از متد [Optional.get] مقدار آن را استخراج می‌کنیم. نتیجه زیر به دست می‌آید:

age max ----
{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}

محاسبه موازی max هیچ‌گونه بهبود عملکردی به همراه ندارد، همان‌طور که در مثال زیر نشان داده شده است: (Example06e1):


package dvp.java8.streams;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;

public class Exemple06e1 {
    public static void main(String[] args) {

        // داده‌ها
        // final long limit = 100L;
        // final boolean verbose = true;
        final long limite = 10_000_000L;
        final boolean verbose = false;

        // تعداد پردازنده‌ها
        System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
        // فهرست اعداد
        long début = new Date().getTime();
        List<Long> nombres = new ArrayList<>();
        for (long i = 0; i < limite; i++) {
            nombres.add(new Random().nextLong());
        }
        System.out.printf("création de la liste des %s nombres en %s ms%n", limite, new Date().getTime() - début);
        // حداکثر اعداد – روش متوالی
        Stream<Long> sNombres = nombres.stream();
        Comparator<Long> compLong = (l1, l2) -> {
            if (verbose) {
                // رشته
                System.out.printf("[%s]", Thread.currentThread().getName());
            }
            // مقایسه
            long v1 = l1.longValue();
            long v2 = l2.longValue();
            if (v1 < v2) {
                return -1;
            } else {
                if (v1 == v2) {
                    return 0;
                } else {
                    return +1;
                }
            }
        };
        début = new Date().getTime();
        // حداکثر طول = sNombres.max(Comparator.naturalOrder()).get();
        long max = sNombres.max(compLong).get();
        System.out.printf("%nmax séquentiel : max=%s, durée (ms)=%s%n", max, new Date().getTime() - début);
    }
}
  • خط ۲۹: یک جریان اعداد تصادفی از نوع Long وجود دارد؛
  • خطوط ۳۰–۴۷: متغیر لامبدا compLong رابط Comparator<Long> را پیاده‌سازی می‌کند. این رابط معمولاً توسط متد [Comparator.naturalOrder()] در خط ۴۹ پیاده‌سازی می‌شود. با این حال، در اینجا می‌خواهیم نخ اجرای برنامه را نمایش دهیم (خطوط ۳۱–۳۳). بنابراین، خودمان این رابط را پیاده‌سازی می‌کنیم؛
  • خط ۵۰: جستجو برای max;

نتایج زیر را به دست می‌آوریم:

 

اگر اکنون خط ۲۷ را با عبارت زیر جایگزین کنیم (Example06e2):


Stream<Long> sNombres = nombres.stream().parallel();

نتایج زیر به دست می‌آیند:

 

اجرای موازی بنابراین کندتر بود. اگر با استفاده از verbose=false تعداد اعداد را به ۱۰ میلیون افزایش دهیم، نتایج زیر را به دست می‌آوریم:

1
2
3
4
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000000 nombres en 3764 ms

max séquentiel : max=9223370471463514417, durée (ms)=53

برای اجرای متوالی:

1
2
3
4
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000000 nombres en 3760 ms

max parallèle : max=9223365260999360873, durée (ms)=77

برای اجرای موازی، که در نتیجه کندتر باقی می‌ماند.

روش [Stream.min] به روشی مشابه استفاده می‌شود:


// سبک‌ترین فرد
affiche("poids min", personnes.stream().min(Comparator.comparingDouble(Personne::getPoids)).get());

5.6.7. [reduce]


// جمعیت کل افراد
affiche("âge total (reduce)", personnes.stream().map(p -> p.getAge()).reduce(0, (a1, a2) -> a1 + a2));

روش [reduce] در بخش 5.3 ارائه شد. خط 2 بالا مجموع سن همه افراد را محاسبه می‌کند. نتیجه به شرح زیر است:

âge total (reduce) ----
60

5.6.8. [sorted]


// افراد به ترتیب صعودی سن
affiche("personnes par âge croissant",
                personnes.stream().sorted(Comparator.comparingInt(Personne::getAge)).collect(Collectors.toList()));
//افراد به ترتیب الفبایی نام خانوادگی
List<Personne> lPersonnes=personnes.stream().sorted((p1, p2) -> p1.getNom().compareTo(p2.getNom())).collect(Collectors.toList());
affiche("personnes par ordre alphabétique des noms", lPersonnes);

روش [sorted] (خطوط ۳ و ۵) دارای امضای زیر است:

 

متد [sorted] به عنوان پارامتر، نوع [Comparator] را که در بخش 5.6.6 برای متدهای min و max توصیف شده است، می‌پذیرد. این امکان را فراهم می‌کند که یک Stream بر اساس مقایسه‌کننده‌ای که به‌عنوان پارامتر به آن ارسال می‌شود، مرتب شود. ما دیده‌ایم که رابط [Comparator] به‌طور پیش‌فرض چندین متد ایستا را فراهم می‌کند که مقایسه‌کننده‌های رایج، به‌ویژه برای اعداد و رشته‌های کاراکتری، را پیاده‌سازی می‌کنند. در اینجا، ما از متد [Comparator.comparingInt] استفاده می‌کنیم که یک نوع ToIntFunction را به عنوان پارامتر می‌پذیرد — یک رابط متد تابعی برای [applyAsInt] — با امضای زیر: int applyAsInt(T t). در اینجا، پارامتر واقعی که در خط ۳ به متد [Comparator.comparingInt] ارسال می‌شود، ارجاع به متد [Personne.age] است که سن شخص را برمی‌گرداند.

رابط [Comparator] هیچ متد ایستا برای مقایسه رشته‌ها ارائه نمی‌دهد. در خط ۵، ما لامبدا خود را می‌سازیم که تنها متد این رابط را پیاده‌سازی می‌کند: int compare(T t1, T t2)


(p1, p2) -> p1.getNom().compareTo(p2.getNom())

این لامبدا نام افراد را مقایسه می‌کند. نتایج به‌دست‌آمده به شرح زیر است:

1
2
3
4
personnes par âge croissant ----
[{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"},{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"},{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}]
personnes par ordre alphabétique des noms ----
[{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"},{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"},{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}]

به نظر می‌رسد اجرای مرتب‌سازی به صورت موازی امکان‌پذیر نیست، همانطور که در کد زیر (Example06f1) نشان داده شده است:


package dvp.java8.streams;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Exemple06f1 {
    // نقشه‌ساز jSON
    static ObjectMapper jsonMapper = new ObjectMapper();

    public static void main(String[] args) throws JsonProcessingException {

        //داده‌ها
        final long limite = 100L;
        final boolean verbose = true;
//         محدودیت نهایی long = 10_000_000L;
//         final boolean verbose = false;

        // تعداد پردازنده‌ها
        System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
        // فهرست اعداد
        long début = new Date().getTime();
        List<Integer> nombres = new ArrayList<>();
        for (long i = 0; i < limite; i++) {
            nombres.add(new Random().nextInt(1000));
        }
        System.out.printf("création de la liste des %s nombres en %s ms%n", limite, new Date().getTime() - début);
        // ترتیب‌دهی اعداد – روش متوالی
        Stream<Integer> sNombres = nombres.stream();
        début = new Date().getTime();
        Comparator<Integer> compInt = (i1, i2) -> {
            if (verbose) {
                // رشته
                System.out.printf("[%s]", Thread.currentThread().getName());
            }
            // مقایسه
            int v1 = i1.intValue();
            int v2 = i2.intValue();
            if (v1 < v2) {
                return +1;
            } else {
                if (v1 == v2) {
                    return 0;
                } else {
                    return -1;
                }
            }
        };
        if (verbose) {
            affiche("nombres", sNombres.sorted(compInt).collect(Collectors.toList()));
        }
        System.out.printf("tri séquentiel : durée (ms)=%s%n", new Date().getTime() - début);
    }

    public static <T> void affiche(String message, T value) throws JsonProcessingException {
        System.out.println(String.format("%s ----", message));
        System.out.println(jsonMapper.writeValueAsString(value));
    }

}
  • خطوط ۳۰–۳۶: یک جریان اعداد تصادفی تولید می‌شود؛
  • خط ۳۲: لامبدا compInt (خطوط ۳۸–۵۵) به متد [sorted] پاس داده می‌شود. این لامبدا اعداد را به ترتیب نزولی مرتب می‌کند و نخ در حال اجرای آن را نمایش می‌دهد.

نتایج به‌دست‌آمده به شرح زیر است:

 

اگر در کد قبلی، خط ۳۶ با عبارت زیر جایگزین شود (Example06f2):


        Stream<Integer> sNombres = nombres.stream().parallel();        

نتایج زیر به دست می‌آیند:

 

به طور شگفت‌آور، متوجه می‌شویم که مرتب‌سازی جریان اعداد با استفاده از یک نخ واحد انجام شده است. هیچ موازی‌سازی‌ای وجود نداشت. یا من چیزی را از قلم انداخته‌ام؟

5.6.9. [anyMatch, noneMatch, allMatch]


//آیا افرادی بالای ۱۰۰ سال وجود دارند؟
affiche("des personnes de + de 100 ans (anyMatch)", personnes.stream().anyMatch(p -> p.getAge() > 100));
// آیا همه افراد ۱۰۰ ساله یا کمتر هستند؟
affiche("des personnes de + de 100 ans (noneMatch)", personnes.stream().noneMatch(p -> p.getAge() > 100));
// آیا همه افراد بالای ۸ سال هستند؟
affiche("des personnes de + de 8 ans (allMatch)", personnes.stream().allMatch(p -> p.getAge() > 8));

خطوط ۲، ۴ و ۶: متدهای [anyMatch, noneMatch, allMatch] به‌عنوان پارامتر نوع Predicate را می‌گیرند که در بخش ۴.۲ توصیف شده است. بنابراین آن‌ها عمل فیلتر کردن را انجام می‌دهند. هر سه یک مقدار بولی بازمی‌گردانند:

  • anyMatch اگر حداقل یک عنصر از Stream وجود داشته باشد که فیلتر را برآورده کند، مقدار true را برمی‌گرداند؛
  • noneMatch مقدار true را برمی‌گرداند اگر هیچ عنصری در Stream وجود نداشته باشد که با فیلتر مطابقت داشته باشد؛
  • allMatch مقدار true را بازمی‌گرداند اگر همهٔ عناصر Stream فیلتر را برآورده کنند؛

نتایج به‌دست‌آمده به شرح زیر است:

1
2
3
4
5
6
des personnes de + de 100 ans (anyMatch) ----
false
des personnes de + de 100 ans (noneMatch) ----
true
des personnes de + de 8 ans (allMatch) ----
true

5.6.10. [collect(Collectors.groupingBy)]


//افراد بر اساس جنسیت گروه‌بندی شده‌اند
affiche("personnes regroupées par sexe", personnes.stream().collect(Collectors.groupingBy(p -> p.getSexe())));

روش [collect] در بخش 5.6.3 توصیف شده است. پارامتر آن پیاده‌سازی رابط [Collector] است. کلاس [Collectors] تعدادی متد ایستا را فراهم می‌کند که رابط [Collector] را پیاده‌سازی می‌کنند. تا کنون از متد [Collectors.toList()] استفاده می‌کردیم. در اینجا از متد استاتیک [Collectors.groupingBy] استفاده می‌کنیم که یک دیکشنری از Stream ایجاد می‌کند. امضای آن به شرح زیر است:

 

متد [groupingBy] یک نوع Map<K,List<T>> را از یک نوع Stream<T> ایجاد می‌کند. کلید K توسط پارامتر متد [groupingBy] که از نوع Function<T,K> است، فراهم می‌شود و تنها متد آن امضای زیر را دارد: K apply(T t). اگر بخواهیم یک دیکشنری با نمایه‌گذاری بر اساس جنسیت فرد ایجاد کنیم، باید تابعی را فراهم کنیم که جنسیت را از یک فرد تولید کند. در اینجا، ما ارجاع به متد [Personne.getSexe] را به‌عنوان پارامتر حقیقی متد [groupingBy] ارسال می‌کنیم. نتایج حاصل به شرح زیر است:

personnes regroupées par sexe ----
{"HOMME":[{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}],"FEMME":[{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"},{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}]}

در خط ۲، ما رشته jSON را از یک فرهنگ لغت که با دو کلید HOMME و FEMME نمایه شده است، دریافت می‌کنیم.

محاسبات موازی هیچ‌گونه افزایش کارایی به همراه ندارد، همان‌طور که در مثال زیر (Example06g1) نشان داده شده است:


package dvp.java8.streams;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Exemple06g1 {

    //بدتر jSON
    static ObjectMapper jsonMapper = new ObjectMapper();

    public static void main(String[] args) throws JsonProcessingException {

        // داده‌ها
        final long limite = 100L;
        final boolean verbose = true;
//         limit نهایی long = 10_000_000L;
//         final boolean verbose = false;

        // تعداد پردازنده‌ها
        System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
        // فهرست اعداد
        long début = new Date().getTime();
        List<Integer> nombres = new ArrayList<>();
        for (long i = 0; i < limite; i++) {
            nombres.add(new Random().nextInt(1000));
        }
        System.out.printf("création de la liste des %s nombres en %s ms%n", limite, new Date().getTime() - début);
        // گروه‌بندی اعداد به صورت صدتایی – روش متوالی
        Stream<Integer> sNombres = nombres.stream();
        Function<Integer, Integer> groupByCent = n -> {
            if (verbose) {
                System.out.printf("[%s]", Thread.currentThread().getName());
            }
            return n / 100;
        };
        début = new Date().getTime();
        // Map<Integer, List<Integer>> lNombres = sNombres.collect(Collectors.grouping@c(number -> number / 100));
        Map<Integer, List<Integer>> lNombres = sNombres.collect(Collectors.groupingBy(groupByCent));
        System.out.printf("%nregroupement séquentiel : durée (ms)=%s%n", new Date().getTime() - début);
        // نتایج
        if (verbose) {
            affiche("nombres regroupés", lNombres);
        }
    }

    public static <T> void affiche(String message, T value) throws JsonProcessingException {
        System.out.println(String.format("%s ----", message));
        System.out.println(jsonMapper.writeValueAsString(value));
    }

}
  • خطوط ۲۳–۳۸: ساخت یک جریان از اعداد limite؛
  • خط ۴۷: اعداد به صورت صدتایی گروه‌بندی شده‌اند. تابع لامبدا در خطوط ۳۹–۴۴ برای نمایش نخ اجرای برنامه استفاده می‌شود؛

نتایج اجرا به شرح زیر است:

 

اگر در کد، خط ۳۸ با خط زیر جایگزین شود (Example06g2):


Stream<Integer> sNombres = nombres.stream().parallel();            

نتایج زیر به دست می‌آیند:

 

می‌توانیم ببینیم که اجرای موازی گروه‌بندی، عملکرد را کاهش داده است.

5.6.11. [distinct]


// حذف عناصر تکراری از یک لیست
affiche("distinct", Stream.of(1, 2, 1).distinct().collect(Collectors.toList()));

روش [distinct] دارای امضای زیر است:

 

برای حذف موارد تکراری از یک فید استفاده می‌شود. متد [Stream.of] (خط ۲) دارای امضای زیر است:

 

این روش برای ایجاد یک Stream از مقادیر صریحاً ارائه‌شده استفاده می‌شود. نتایج اجرای آن به شرح زیر است:

distinct ----
[1,2]

5.6.12. [flatMap]


// از یک Stream<Stream<T>>، یک Stream<T> ایجاد می‌کنیم
affiche("flatMap", Stream.of(1, 2, 3).flatMap(i -> Stream.of(i, i + 10)).collect(Collectors.toList()));

متد [flatMap] دارای امضای زیر است:

 

متد [flatMap] به‌عنوان پارامتر تابعی را می‌پذیرد که:

  • به‌عنوان پارامتر یک عنصر از نوع T از Stream را می‌پذیرد؛
  • یک جریان Stream<R> را بازمی‌گرداند؛

اگر به جای متد [flatMap]، اگر به جای متد [flatMap]، از متد [map] که در بخش 5.5 توصیف شده است، استفاده می‌کردیم، نتیجه یک نوع Stream<Stream<R>> می‌بود، که در آن هر عنصر از نوع T در جریان اولیه، یک عنصر Stream<R> ایجاد می‌کرد. آیا روش [flatMap] از سوی دیگر، نوع Stream<R> را تولید می‌کند؟ این روش جریان‌های مختلف Stream<R> را به یک جریان واحد تبدیل می‌کند. این موضوع با نتایج اجرای کد قبلی نشان داده شده است:

flatMap ----
[1,11,2,12,3,13]

واریانت‌های تخصصی [flatMap] وجود دارند:


//از یک Stream<IntStream>، یک IntStream ایجاد می‌کنیم و مجموع آن را محاسبه می‌کنیم
affiche("flatMapToInt", Stream.of(1, 2, 3).flatMapToInt(i -> IntStream.of(i, i + 10)).sum());

متد [flatMapToInt] دارای امضای زیر است:

 

متد [flatMapToInt] یک تابع را به‌عنوان پارامتر می‌پذیرد که یک IntStream از نوع زیر را برمی‌گرداند:

 

IntStream خوراک مشتق‌شده از int است. این نوع نسبت به نوع Stream<Integer> ترجیح دارد زیرا پردازش آن از باکسینگ و آنباکسینگ بین انواع Integer و int جلوگیری می‌کند. این رابط بسیاری از متدها را از نوع Stream<T> به ارث می‌برد و متدهای دیگری را نیز اضافه می‌کند، از جمله متد [sum] که در بالا ذکر شد و عناصر IntStream را جمع می‌کند.

کد زیر استفاده از روش مشابه [flatMapToDouble] را نشان می‌دهد:


//از یک Stream<DoubleStream>، یک DoubleStream ایجاد می‌کنیم و سپس یک آرایه
affiche("flatMapToDouble", Stream.of(1, 2, 3).flatMapToDouble(i -> DoubleStream.of(i, i * 1.2)).toArray());

متد [DoubleStream.toArray] به شما امکان می‌دهد از نوع DoubleStream به نوع double[] تبدیل کنید.

نتایج این دو مثال به شرح زیر است:

1
2
3
4
flatMapToInt ----
42
flatMapToDouble ----
[1.0,1.2,2.0,2.4,3.0,3.5999999999999996]

مثال زیر افزایش عملکرد حاصل از تغییر از نوع Stream<Long> به نوع LongStream را نشان می‌دهد (Example06i1):


package dvp.java8.streams;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Exemple06i1 {
    public static void main(String[] args) {

        final long limite = 10_000_000L;
        //تعداد پردازنده‌ها
        System.out.printf("La JVM a détecté [%s] coeurs sur votre machine%n", Runtime.getRuntime().availableProcessors());
        // فهرست اعداد
        long début = new Date().getTime();
        List<Long> nombres = new ArrayList<>();
        for (long i = 0; i < limite; i++) {
            nombres.add(i);
        }
        System.out.printf("création de la liste des %s nombres en %s ms%n", limite, new Date().getTime() - début);
        // جمع اعداد – روش متوالی
        début = new Date().getTime();
        long somme = nombres.stream().reduce(0L, (s, i) -> s + i);
        System.out.printf("somme séquentielle du Stream<Integer> : somme=%s, durée (ms)=%s%n", somme, new Date().getTime() - début);
    }
}
  • خط ۲۲: محاسبه مجموع یک جریان اعداد از نوع Long؛

نتایج زیر به دست می‌آیند:

1
2
3
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000000 nombres en 4537 ms
somme séquentielle du Stream<Integer> : somme=49999995000000, durée (ms)=226

اکنون، بیایید خط ۲۲ را با موارد زیر جایگزین کنیم (مثال ۰۶i2):


long somme = nombres.stream().mapToLong(n -> n.longValue()).sum();

متد Stream&lt;Integer&gt;.mapToLong به ما امکان می‌دهد تا یک استریم از نوع LongStream حاوی عناصر از نوع ابتدایی long را به دست آوریم، که سپس با استفاده از تابع sum جمع می‌کنیم. این نتایج زیر را به دست می‌دهد:

1
2
3
La JVM a détecté [8] coeurs sur votre machine
création de la liste des 10000000 nombres en 4511 ms
somme séquentielle du LongStream : somme=49999995000000, durée (ms)=99

افزایش عملکرد واضح است.

5.6.13. روش‌های جریان عدد اولیه


// بیشترین مقدار در یک جریان اعداد صحیح
affiche("IntStream max", IntStream.of(1, 10, 8).max());
//حداقل یک جریان از اعداد اعشاری
affiche("DoubleStream min", DoubleStream.of(1.5, 10.4, 8.9).min());
// میانگین یک جریان اعداد صحیح
affiche("IntStream average", IntStream.of(1, 10, 8).average().getAsDouble());
//آمار بر روی یک جریان اعداد صحیح
affiche("IntStream summaryStatistics", IntStream.of(1, 10, 8).summaryStatistics());

جریان‌های اولیهٔ مقادیر (int, long, double) متدهایی را ارائه می‌دهند که برای این نوع‌ها مناسب‌سازی شده‌اند. نتیجهٔ اجرای کد قبلی به شرح زیر است:

1
2
3
4
5
6
7
8
IntStream max ----
{"asInt":10,"present":true}
DoubleStream min ----
{"asDouble":1.5,"present":true}
IntStream average ----
6.333333333333333
IntStream summaryStatistics ----
{"count":3,"sum":19,"min":1,"max":10,"average":6.333333333333333}
  • نتیجه خط ۲ کد، نوع OptionalInt است که مشابه نوع Optional<Integer> می‌باشد. مقدار ذخیره شده در این شیء را می‌توان با استفاده از متد [getAsInt()] بازیابی کرد. وجود یک مقدار را می‌توان با استفاده از متد [isPresent()] بررسی کرد. خط ۲ نتایج به این معنا نیست که کلاس [OptionalInt] فیلدهایی با نام [asInt, present] دارد. به طور پیش‌فرض، کتابخانه jSON از تمام متدهای عمومی getX و isY شیئی که باید سریال شود، برای سریال کردن آن به jSON استفاده می‌کند. و در اینجا، در واقع یک متد [getAsInt] و یک متد دیگر [isPresent] وجود دارد، هرچند فیلدهای [asInt, present] وجود ندارند؛
  • نتیجه خط ۴ کد از نوع OptionalDouble است که مشابه نوع Optional<Double> است؛
  • نتیجه خط ۶ کد یک نوع OptionalDouble است که مقدار آن را می‌توان با استفاده از متد [getAsDouble()] به دست آورد. متد [average] میانگین دنباله اعداد را محاسبه می‌کند؛
  • نتیجه خط ۸ کد از نوع IntSummaryStatistics است که به شرح زیر تعریف شده است:
 

می‌توانیم ببینیم که شیء حاصل IntSummaryStatistics جزئیات مختلفی را در مورد جریان اعداد، مانند تعداد مقادیر، مجموع، حداکثر، حداقل و میانگین ارائه می‌دهد.