JavaRush /בלוג Java /Random-HE /הפסקת קפה מס' 177. מדריך מפורט ל-Java Stream ב-Java 8

הפסקת קפה מס' 177. מדריך מפורט ל-Java Stream ב-Java 8

פורסם בקבוצה
מקור: Hackernoon פוסט זה מספק מדריך מפורט על עבודה עם Java Stream יחד עם דוגמאות קוד והסברים. הפסקת קפה מס' 177.  מדריך מפורט ל-Java Stream ב-Java 8 - 1

מבוא ל-Java Threads ב-Java 8

Java Streams, שהוצגו כחלק מ-Java 8, משמשים לעבודה עם אוספי נתונים. הם אינם מבנה נתונים בעצמם, אך ניתן להשתמש בהם כדי להזין מידע ממבני נתונים אחרים על ידי הזמנה וצינור כדי להפיק תוצאה סופית. הערה: חשוב לא לבלבל בין Stream ו-Thread, מכיוון שברוסית מתייחסים לרוב לשני המונחים באותו תרגום "זרימה". Stream מציין אובייקט לביצוע פעולות (לרוב העברה או אחסון נתונים), בעוד Thread (תרגום מילולי - thread) מציין אובייקט המאפשר ביצוע של קוד תוכנית מסוים במקביל לענפי קוד אחרים. מכיוון ש-stream אינו מבנה נתונים נפרד, הוא לעולם לא משנה את מקור הנתונים. לזרמי Java יש את התכונות הבאות:
  1. ניתן להשתמש ב-Java Stream באמצעות חבילת "java.util.stream". ניתן לייבא אותו לסקריפט באמצעות הקוד:

    import java.util.stream.* ;

    באמצעות קוד זה, נוכל גם ליישם בקלות מספר פונקציות מובנות ב-Java Stream.

  2. Java Stream יכול לקבל קלט מאוספי נתונים כגון אוספים ומערכים ב-Java.

  3. Java Stream אינו דורש שינוי מבנה נתוני הקלט.

  4. Java Stream לא משנה את המקור. במקום זאת, הוא מייצר פלט באמצעות שיטות צינור מתאימות.

  5. זרמי Java כפופים לפעולות ביניים ומסוף, עליהם נדון בסעיפים הבאים.

  6. ב-Java Stream, פעולות ביניים מתבצעות בצנרת ומתרחשות בפורמט הערכה עצלן. הם מסתיימים בפונקציות מסוף. זה מהווה את הפורמט הבסיסי לשימוש ב-Java Stream.

בסעיף הבא, נבחן את השיטות השונות המשמשות ב-Java 8 ליצירת זרם Java לפי הצורך.

יצירת זרם Java ב-Java 8

ניתן ליצור שרשורי Java בכמה דרכים:

1. יצירת זרם ריק בשיטת Stream.empty()

אתה יכול ליצור זרם ריק לשימוש מאוחר יותר בקוד שלך. אם אתה משתמש בשיטת Stream.empty() , ייווצר זרם ריק, שאינו מכיל ערכים. הזרם הריק הזה יכול להיות שימושי אם ברצוננו לדלג על חריג מצביע null בזמן ריצה. כדי לעשות זאת אתה יכול להשתמש בפקודה הבאה:
Stream<String> str = Stream.empty();
ההצהרה שלעיל תיצור זרם ריק בשם str ללא אלמנטים כלשהם בתוכו. כדי לאמת זאת, פשוט בדוק את מספר או גודל הזרם באמצעות המונח str.count() . לדוגמה,
System.out.println(str.count());
כתוצאה מכך, נקבל 0 בפלט .

2. צור זרם באמצעות שיטת Stream.builder() עם מופע Stream.Builder

אנו יכולים גם להשתמש ב-Stream Builder כדי ליצור זרם באמצעות תבנית העיצוב של ה-Builder. הוא מיועד לבנייה שלב אחר שלב של חפצים. בואו נראה כיצד נוכל ליצור מופע של זרם באמצעות בונה זרמים .
Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Stream<Integer> numStream = numBuilder.build();
באמצעות קוד זה, אתה יכול ליצור זרם בשם numStream המכיל רכיבי int . הכל נעשה די מהר הודות למופע Stream.Builder שנקרא numBuilder שנוצר ראשון.

3. צור זרם עם הערכים שצוינו בשיטת Stream.of()

דרך נוספת ליצור זרם כוללת שימוש בשיטת Stream.of() . זוהי דרך פשוטה ליצור זרם עם ערכים נתונים. הוא מצהיר וגם מאתחל את השרשור. דוגמה לשימוש בשיטת Stream.of() ליצירת זרם:
Stream<Integer> numStream = Stream.of(1, 2, 3);
קוד זה יצור זרם המכיל רכיבי int , בדיוק כמו שעשינו בשיטה הקודמת באמצעות Stream.Builder . כאן יצרנו זרם ישירות באמצעות Stream.of() עם ערכים מוגדרים מראש [1, 2 ו-3] .

4. צור זרם ממערך קיים בשיטת Arrays.stream()

שיטה נפוצה נוספת ליצירת שרשור כוללת שימוש במערכים ב-Java. הזרם כאן נוצר ממערך קיים בשיטת Arrays.stream() . כל רכיבי המערך מומרים לרכיבי זרם. הנה דוגמה טובה:
Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);
קוד זה יפיק numStream המכיל את התוכן של מערך שנקרא arr, שהוא מערך שלמים.

5. מיזוג שני זרמים קיימים בשיטת Stream.concat()

שיטה נוספת שניתן להשתמש בה ליצירת זרם היא השיטה Stream.concat() . הוא משמש לשילוב שני חוטים ליצירת חוט בודד. שני הזרמים משולבים לפי הסדר. זה אומר שהחוט הראשון מגיע ראשון, ואחריו החוט השני, וכן הלאה. דוגמה לשרשור כזה נראית כך:
Stream<Integer> numStream1 = Stream.of(1, 2, 3, 4, 5);

Stream<Integer> numStream2 = Stream.of(1, 2, 3);

Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);
ההצהרה לעיל תיצור זרם סופי בשם combinedStream המכיל אלמנטים של הזרם הראשון numStream1 והזרם השני numStream2 בזה אחר זה .

סוגי פעולות עם Java Stream

כפי שכבר ציינו, ניתן לבצע שני סוגי פעולות עם Java Stream ב-Java 8: בינוני ומסוף. בואו נסתכל על כל אחד מהם ביתר פירוט.

פעולות ביניים

פעולות ביניים מייצרות זרם פלט ומבוצעות רק כאשר נתקלים בפעולת טרמינל. המשמעות היא שפעולות ביניים מבוצעות בעצלתיים, בצנרת, וניתן להשלים אותן רק על ידי פעולת טרמינל. על הערכה עצלנית וצנרת תלמדו קצת מאוחר יותר. דוגמאות לפעולות ביניים הן השיטות הבאות: filter() , map() , different() , peek() , sorted() ועוד כמה.

תפעול מסוף

פעולות טרמינל משלימות את ביצוע פעולות ביניים וגם מחזירות את התוצאות הסופיות של זרם הפלט. מכיוון שפעולות טרמינל מאותתות על סיום הביצוע העצל והצנרת, לא ניתן להשתמש בחוט זה שוב לאחר שהוא עבר פעולת טרמינל. דוגמאות לפעולות מסוף הן השיטות הבאות: forEach() , collect() , count() , reduce() וכן הלאה.

דוגמאות לפעולות עם Java Stream

פעולות ביניים

הנה כמה דוגמאות לכמה פעולות ביניים שניתן להחיל על זרם Java:

לְסַנֵן()

שיטה זו משמשת לסינון אלמנטים מזרם התואמים לפרדיקט ספציפי ב-Java. הפריטים המסונננים האלה מרכיבים זרם חדש. בואו נסתכל על דוגמה כדי להבין טוב יותר.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);
סיכום:
[98]
הסבר: בדוגמה זו, ניתן לראות שאפילו אלמנטים (המתחלקים ב-2) מסוננים בשיטת filter() ומאוחסנים ברשימת מספרים שלמים numStream , שתוכנה מודפס מאוחר יותר. מכיוון ש-98 הוא המספר השלם הזוגי היחיד בזרם, הוא מודפס בפלט.

מַפָּה()

שיטה זו משמשת ליצירת זרם חדש על ידי ביצוע פונקציות ממופה על אלמנטים של זרם הקלט המקורי. אולי לזרם החדש יש סוג נתונים אחר. הדוגמה נראית כך:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);
סיכום:
[86, 130, 2, 196, 126]
הסבר: כאן אנו רואים ששיטת map() משמשת פשוט להכפיל כל רכיב בזרם numStream . כפי שניתן לראות מהפלט, כל אחד מהאלמנטים בזרם הוכפל בהצלחה.

מוּבהָק()

שיטה זו משמשת לאחזור רק אלמנטים בודדים בזרם על ידי סינון כפילויות. דוגמה לאותו דבר נראית כך:
Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
סיכום:
[43, 65, 1, 98, 63]
הסבר: במקרה זה, נעשה שימוש בשיטת different() עבור numStream . הוא מאחזר את כל האלמנטים הבודדים ב-numList על ידי הסרת כפילויות מהזרם. כפי שניתן לראות מהפלט, אין כפילויות, בניגוד לזרם הקלט, שבהתחלה היו לו שני כפילויות (63 ו-1).

לְהָצִיץ()

שיטה זו משמשת למעקב אחר שינויים ביניים לפני ביצוע פעולת טרמינל. משמעות הדבר היא שניתן להשתמש ב-peek() כדי לבצע פעולה בכל אלמנט של זרם כדי ליצור זרם שעליו ניתן לבצע פעולות ביניים נוספות.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList()); System.out.println(nList);
סיכום:
ממופה: 430 ממופה: 650 ממופה: 10 ממופה: 980 ממופה: 630 [430, 650, 10, 980, 630]
הסבר: כאן נעשה שימוש בשיטת peek() ליצירת תוצאות ביניים כאשר שיטת map() מיושמת על רכיבי הזרם. כאן אנו יכולים לשים לב שעוד לפני השימוש בפעולת הטרמינל collect() כדי להדפיס את התוכן הסופי של הרשימה ב- print statement , התוצאה עבור כל מיפוי רכיבי זרם מודפסת ברצף מראש.

מְמוּיָן()

השיטה sorted() משמשת למיון האלמנטים של זרם. כברירת מחדל, הוא ממיין אלמנטים בסדר עולה. ניתן גם לציין סדר מיון מסוים כפרמטר. יישום לדוגמה של שיטה זו נראה כך:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
סיכום:
1 43 ​​63 65 98
הסבר: כאן, השיטה sorted() משמשת כדי למיין את רכיבי הזרם בסדר עולה כברירת מחדל (מכיוון שלא צוין סדר ספציפי). ניתן לראות שהפריטים המודפסים בפלט מסודרים בסדר עולה.

תפעול מסוף

הנה כמה דוגמאות לכמה פעולות מסוף שניתן להחיל על זרמי Java:

לכל אחד()

השיטה forEach() משמשת כדי לחזור על כל האלמנטים של זרם ולבצע את הפונקציה בכל אלמנט אחד אחד. זה פועל כחלופה להצהרות לולאה כגון for , while ואחרות. דוגמא:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
סיכום:
43 65 1 98 63
הסבר: כאן נעשה שימוש בשיטת forEach() כדי להדפיס כל רכיב בזרם אחד אחד.

לספור()

שיטת count() משמשת כדי לאחזר את המספר הכולל של אלמנטים הקיימים בזרם. זה דומה לשיטת size() , המשמשת לעתים קרובות כדי לקבוע את המספר הכולל של אלמנטים באוסף. דוגמה לשימוש בשיטת count() עם זרם Java היא כדלקמן:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
סיכום:
5
הסבר: מכיוון ש- numStream מכיל 5 אלמנטים שלמים, שימוש בשיטת count() עליו יוציא 5.

לאסוף()

שיטת collect() משמשת לביצוע הפחתות הניתנות לשינוי של רכיבי זרם. ניתן להשתמש בו כדי להסיר תוכן מזרם לאחר השלמת העיבוד. הוא משתמש במחלקה Collector כדי לבצע הפחתות .
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);
סיכום:
[43, 65, 1, 63]
הסבר: בדוגמה זו, כל הרכיבים האי-זוגיים בזרם מסוננים ונאספים/מצומצמים לרשימה בשם אי-זוגי . בסוף מודפסת רשימה של מוזרים.

min() ומקס ()

ניתן להשתמש בשיטת min() , כפי שהשם מרמז, בזרם כדי למצוא את האלמנט המינימלי בו. באופן דומה, ניתן להשתמש בשיטת max() כדי למצוא את האלמנט המקסימלי בזרם. בואו ננסה להבין כיצד ניתן להשתמש בהם באמצעות דוגמה:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);
numStream = Stream.of(43, 65, 1, 98, 63); int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);
סיכום:
האלמנט הקטן ביותר: 1 האלמנט הגדול ביותר: 98
הסבר: בדוגמה זו, הדפסנו את האלמנט הקטן ביותר ב-numStream בשיטת min () ואת האלמנט הגדול ביותר בשיטת max() . שימו לב שכאן, לפני השימוש בשיטת max() , הוספנו שוב אלמנטים ל- numStream . הסיבה לכך היא ש-min() היא פעולת טרמינל ומשמידה את התוכן של הזרם המקורי, ומחזירה רק את התוצאה הסופית (שבמקרה זה הייתה המספר השלם "הקטן ביותר").

findAny() ו- findFirst()

findAny() מחזיר כל רכיב בזרם כאופציונלי . אם הזרם ריק, הוא יחזיר גם ערך אופציונלי , שיהיה ריק. findFirst() מחזיר את הרכיב הראשון של הזרם כאופציונלי . כמו בשיטת findAny() , גם שיטת findFirst() מחזירה פרמטר אופציונלי ריק אם הזרם המתאים ריק. הבה נסתכל על הדוגמה הבאה המבוססת על שיטות אלה:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); Optional<Integer> opt = numStream.findFirst();System.out.println(opt); numStream = Stream.empty(); opt = numStream.findAny();System.out.println(opt);
סיכום:
אופציונלי[43] אופציונלי.ריק
הסבר: כאן, במקרה הראשון, המתודה findFirst() מחזירה את האלמנט הראשון של הזרם כאופציונלי . לאחר מכן, כאשר השרשור מוקצה מחדש כשרשור ריק, השיטה findAny() מחזירה ריק Optional .

allMatch() , anyMatch() ו- noneMatch()

השיטה allMatch() משמשת כדי לבדוק אם כל הרכיבים בזרם תואמים לפרדיקט מסוים ומחזירה את הערך הבוליאני true אם כן, אחרת מחזירה false . אם הזרם ריק, הוא מחזיר אמת . השיטה anyMatch() משמשת כדי לבדוק אם אחד מהרכיבים בזרם תואם לפרדיקט מסוים. זה מחזיר אמת אם כן, לא נכון אחרת. אם הזרם ריק, הוא מחזיר false . השיטה noneMatch() מחזירה true אם אף אלמנט בזרם לא תואם את הפרדיקט, ו- false אחרת. דוגמה להמחשה נראית כך:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); boolean flag = numStream.allMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.anyMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.noneMatch(n -> n==1);System.out.println(flag);
סיכום:
שקר אמיתי שקר
הסבר: עבור זרם numStream המכיל 1 כאלמנט, המתודה allMatch() מחזירה false מכיוון שכל האלמנטים אינם 1, אלא רק אחד מהם. המתודה anyMatch() מחזירה true כי לפחות אחד מהאלמנטים הוא 1. המתודה noneMatch() מחזירה false כי 1 קיים למעשה כאלמנט בזרם זה.

הערכות עצלות ב-Java Stream

הערכה עצלנית מובילה לאופטימיזציות בעבודה עם Java Streams ב-Java 8. הן כוללות בעיקר דחיית פעולות ביניים עד להיתקל בפעולת טרמינל. הערכה עצלנית אחראית למניעת בזבוז מיותר של משאבים בחישובים עד שתהיה צורך בתוצאה בפועל. זרם הפלט הנובע מפעולות ביניים נוצר רק לאחר סיום פעולת הטרמינל. הערכה עצלנית עובדת עם כל פעולות הביניים בזרמי Java. שימוש מאוד שימושי בהערכה עצלנית מתרחש כאשר עובדים עם זרמים אינסופיים. כך נמנעים הרבה עיבודים מיותרים.

צינורות ב-Java Stream

צינור ב-Java Stream מורכב מזרם קלט, אפס או יותר פעולות ביניים מסודרות בזו אחר זו, ולבסוף פעולת טרמינל. פעולות ביניים ב-Java Streams מבוצעות בעצלתיים. זה הופך את פעולות הביניים בצנרת לבלתי נמנעות. עם צינורות, שהם בעצם פעולות ביניים משולבות לפי הסדר, מתאפשר ביצוע עצל. צינורות עוזרים לעקוב אחר פעולות ביניים שיש לבצע לאחר שלבסוף נתקלים בפעולת טרמינל.

סיכום

כעת נסכם את מה שלמדנו היום. במאמר זה:
  1. בדקנו מה הם זרמי Java.
  2. לאחר מכן למדנו טכניקות רבות ושונות ליצירת שרשורי Java ב-Java 8.
  3. למדנו שני סוגים עיקריים של פעולות (פעולות ביניים ופעולות טרמינל) שניתן לבצע על זרמי Java.
  4. לאחר מכן הסתכלנו בפירוט על כמה דוגמאות של פעולות ביניים ופעולות טרמינליות כאחד.
  5. בסופו של דבר למדנו יותר על הערכה עצלנית ולבסוף למדנו על צנרת בשרשורי Java.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION