אנלוגיות API של 8 Java Stream ב- Kotlin

1. הקדמה

ג'אווה 8 הציגה את הרעיון של זרמים להיררכיית האוסף. אלה מאפשרים עיבוד חזק מאוד של נתונים בצורה קריאה מאוד, תוך שימוש בכמה מושגי תכנות פונקציונליים כדי לגרום לתהליך לעבוד.

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

2. ג'אווה מול קוטלין

ב- Java 8, ניתן להשתמש ב- API המהודר החדש רק בעת אינטראקציה עם java.util.stream.Stream מקרים.

הדבר הטוב הוא שכל האוספים הסטנדרטיים - כל דבר שמיישם java.util.Collection - יש שיטה מסוימת זרם() שיכול לייצר א זרם למשל.

חשוב לזכור כי זרם אינו א אוסף.זה לא מיישם java.util.Collection והיא אינה מיישמת שום סמנטיקה רגילה של אוספים בג'אווה. זה דומה יותר לחד פעמי איטרטור בכך שהוא נגזר מא אוסף ומשמש לעבודה דרכו, ביצוע פעולות על כל אלמנט שנראה.

בקוטלין, כל סוגי האוספים כבר תומכים בפעולות אלה בלי צורך להמיר אותם קודם. המרה נחוצה רק אם סמנטיקה של האוסף שגויה - למשל, א מַעֲרֶכֶת בעל אלמנטים ייחודיים אך אינו מסודר.

יתרון אחד בכך הוא שאין צורך בהמרה ראשונית מ- אוסף לתוך זרם, ואין צורך בהמרה סופית מא זרם חזרה לאוסף - באמצעות לאסוף() שיחות.

לדוגמא, ב- Java 8 נצטרך לכתוב את הדברים הבאים:

someList .stream () .map () // כמה פעולות .collect (Collectors.toList ());

המקבילה בקוטלין היא מאוד פשוטה:

someList .map () // כמה פעולות

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

לדוגמה, הדברים הבאים לא יעבדו:

זרם someIntegers = integers.stream (); someIntegers.forEach (...); someIntegers.forEach (...); // יוצא מן הכלל

בקוטלין, העובדה שכולם רק אוספים רגילים פירושה שבעיה זו לעולם לא מתעוררת. ניתן להקצות מצב ביניים למשתנים ולשתף אותם במהירות, ופשוט עובד כמו שהיינו מצפים.

3. רצפים עצלים

אחד הדברים המרכזיים ב- Java 8 זרמים היא שהם מוערכים בעצלתיים. המשמעות היא שלא תבוצע יותר עבודה מהנדרש.

זה שימושי במיוחד אם אנו מבצעים פעולות שעשויות להיות יקרות על האלמנטים ב זרם, או שזה מאפשר לעבוד עם רצפים אינסופיים.

לדוגמה, IntStream.generate יפיק פוטנציאל אינסופי זרם של מספרים שלמים. אם נתקשר findFirst () עליו נקבל את האלמנט הראשון ולא נתקל בלופ אינסופי.

בקוטלין אוספים נלהבים, ולא עצלים. היוצא מן הכלל כאן הוא סדר פעולות, אשר אכן מעריך בעצלתיים.

זו הבחנה חשובה שיש לציין, כפי שמראה הדוגמה הבאה:

val result = listOf (1, 2, 3, 4, 5). מפה {n -> n * n}. פילטר {n -> n <10}. first ()

גרסת הקוטלין לכך תבצע חמש מַפָּה() פעולות, חמש לְסַנֵן() פעולות ואז לחלץ את הערך הראשון. גרסת Java 8 תבצע רק אחת מַפָּה() ואחד לְסַנֵן() מכיוון שמבחינת הפעולה האחרונה אין צורך יותר.

ניתן להמיר את כל האוספים בקוטלין לרצף עצלן באמצעות asSequence () שיטה.

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

4. ג'אווה 8 זרם פעולות

ב- Java 8, זרם הפעולות מחולקות לשתי קטגוריות:

  • ביניים ו
  • מָסוֹף

פעולות ביניים בעצם ממירות אחת זרם לתוך עצלן אחר - למשל, א זרם מכל המספרים השלמים לתוך a זרם מכל המספרים השלמים אפילו.

אפשרויות המסוף הן השלב האחרון של זרם שרשרת שיטות ולהפעיל את העיבוד בפועל.

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

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

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

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

מתוך פעולות אלה, יש כמה שעובדות בדיוק אותו דבר - לְסַנֵן(), מַפָּה(), flatMap (), מוּבהָק() ו מְמוּיָן() - וחלקם עובדים זהים רק עם שמות שונים - לְהַגבִּיל() עכשיו לקחת, ו לדלג() עכשיו יְרִידָה(). לדוגמה:

val oddSquared = listOf (1, 2, 3, 4, 5). מסנן {n -> n% 2 == 1} // 1, 3, 5. מפה {n -> n * n} // 1, 9 , 25. Drop (1) // 9, 25. Take (1) // 9

זה יחזיר את הערך היחיד "9" - 3².

לחלק מהפעולות הללו יש גם גרסה נוספת - בסיומת המילה "ל" - שיוצא לאוסף מסופק במקום לייצר אחד חדש.

זה יכול להיות שימושי לעיבוד מספר אוספי קלט לאותו אוסף פלט, למשל:

val target = mutableList () listOf (1, 2, 3, 4, 5) .filterTo (target) {n -> n% 2 == 0}

זה יכניס את הערכים "2" ו- "4" לרשימה "היעד".

הפעולה היחידה שבדרך כלל אין לה החלפה ישירה היא לְהָצִיץ() - משמש ב- Java 8 כדי לחזור על הערכים ב- זרם באמצע צינור עיבוד מבלי להפריע לזרימה.

אם אנו משתמשים בעצלן סדר פעולות במקום אוסף להוט, אז יש בכל אחד() פונקציה אשר מחליפה באופן ישיר את לְהָצִיץ פוּנקצִיָה. זה קיים רק בשיעור זה, ולכן עלינו להיות מודעים לאיזה סוג אנו משתמשים כדי שהוא יעבוד.

ישנן גם וריאציות נוספות על פעולות הביניים הסטנדרטיות המקלות על החיים. לדוגמא, ה לְסַנֵן לפעולה יש גרסאות נוספות filterNotNull (), filterIsInstance (), filterNot () ו filterIndexed ().

לדוגמה:

listOf (1, 2, 3, 4, 5) .map {n -> n * (n + 1) / 2} .mapIndexed {(i, n) -> "מספר משולש $ i: $ n"}

זה יפיק את חמשת המספרים המשולשים הראשונים, בצורה "המספר המשולש 3: 6"

הבדל חשוב נוסף הוא באופן שבו flatMap הפעולה עובדת. ב- Java 8, פעולה זו נדרשת להחזרת a זרם למשל, ואילו בקוטלין הוא יכול להחזיר כל סוג אוסף. זה מקל על העבודה איתם.

לדוגמה:

val letters = listOf ("This", "Is", "An", "דוגמה") .flatMap {w -> w.toCharArray ()} // מפיק רשימת .filter {c -> Character.isUpperCase (c) }

ב- Java 8 יהיה צורך לעטוף את השורה השנייה Arrays.toStream () כדי שזה יעבוד.

4.2. פעולות טרמינל

לכל פעולות הטרמינל הסטנדרטיות מממשק ה- API של Java 8 Streams יש החלפות ישירות בקוטלין, למעט היחיד של לאסוף.

לשניים מהם יש שמות שונים:

  • anyMatch () ->כל()
  • allMatch () ->את כל()
  • noneMatch () ->אף אחד()

לחלק מהם יש וריאציות נוספות לעבוד עם האופן שבו קוטלין יש הבדלים - יש ראשון() ו firstOrNull (), איפה ראשון זורק אם האוסף ריק, אך מחזיר סוג שאינו מבוטל אחרת.

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

זה מאפשר שרירותיות אַסְפָן להיות מסופק, אשר יסופק עם כל האלמנטים באוסף וייצור תפוקה כלשהי. אלה משמשים מה אספנים כיתת עוזר, אך אנו יכולים לכתוב בעצמנו במידת הצורך.

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

היוצא מן הכלל היחיד כאן הוא מסכם כפול/סיכום Int/סיכום ארוך שיטות - שמייצרות ממוצע, ספירה, דקות, מקסימום וסכום בבת אחת. כל אחד מאלה ניתן לייצר בנפרד - אם כי ברור שיש לכך עלות גבוהה יותר.

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

5. פעולות נוספות בקוטלין

Kotlin מוסיף כמה פעולות נוספות לאוספים שאינם אפשריים ב- Java 8 מבלי ליישם אותן בעצמנו.

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

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

יש גם כמה פעולות שמנצלות את ביטחונו האפס של קוטלין באופן מפורש - למשל; אנחנו יכולים לבצע א filterNotNull () על רשימה להחזיר א רשימה, שם כל האפסים מוסרים.

פעולות נוספות בפועל שניתן לבצע בקוטלין אך לא ב- Java 8 Streams כוללות:

  • רוכסן() ו לִפְתוֹחַ() - משמשים לשילוב שני אוספים לרצף זוגות אחד, ולהפך להמיר אוסף זוגות לשני אוספים
  • חָבֵר - משמש להמרת אוסף למפה על ידי אספקת למבדה להמרת כל ערך באוסף לצמד מפתח / ערך במפה שהתקבלה

לדוגמה:

val numbers = listOf (1, 2, 3) מילות val = listOf ("one", "two", "three") numbers.zip (מילים)

זה מייצר א רשימה, עם ערכים 1 ל"אחד ", 2 ל"שניים" ו 3 עד "שלוש".

ריבועי val = listOf (1, 2, 3, 4,5). נקשר {n -> n ל- n * n}

זה מייצר א מַפָּה, כאשר המקשים הם המספרים 1 עד 5, והערכים הם הריבועים של אותם ערכים.

6. סיכום

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

בנוסף, קוטלין מוסיף גמישות רבה יותר לאופן שבו זה עובד, על ידי הוספת פעולות נוספות שניתן להשתמש בהן וריאציה רבה יותר על הפעולות הקיימות.

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