מבוא ל- Vavr

1. סקירה כללית

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

Vavr הוא א ספרייה פונקציונלית עבור Java 8+ המספקת סוגי נתונים בלתי ניתנים לשינוי ומבני בקרה פונקציונליים.

1.1. תלות של Maven

על מנת להשתמש ב- Vavr, עליך להוסיף את התלות:

 io.vavr vavr 0.9.0 

מומלץ להשתמש תמיד בגרסה האחרונה. אתה יכול לקבל את זה על ידי קישור זה.

2. אפשרות

המטרה העיקרית של Option היא לחסל בדיקות אפס בקוד שלנו על ידי מינוף מערכת מסוג Java.

אוֹפְּצִיָה הוא מיכל עצמים ב- Vavr עם מטרת סיום דומה כמו Optional ב- Java 8. Vavr's אוֹפְּצִיָה מכשירים ניתנת לסידור, ניתנת לניתוח, ויש לו ממשק API עשיר יותר.

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

@Test ציבורי בטל givenValue_whenNullCheckNeeded_thenCorrect () {Object possibleNullObj = null; if (possibleNullObj == null) {possibleNullObj = "someDefaultValue"; } assertNotNull (possibleNullObj); }

ללא בדיקות, היישום יכול לקרוס בגלל פשוט NPE:

@Test (צפוי = NullPointerException.class) חלל ציבורי givenValue_whenNullCheckNeeded_thenCorrect2 () {אובייקט אפשריullObj = null; assertEquals ("somevalue", possibleNullObj.toString ()); }

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

אוֹפְּצִיָה פותר בעיה זו על ידי ביטול מוחלט אפסים והחלפתם בהתייחסות אובייקט חוקית לכל תרחיש אפשרי.

עם אוֹפְּצִיָה א ריק הערך יוערך למופע של אף אחד, בעוד שערך שאינו אפס יערך למופע של כמה:

@Test הציבור בטל givenValue_whenCreatesOption_thenCorrect () {Option noneOption = Option.of (null); אפשרות someOption = Option.of ("val"); assertEquals ("None", noneOption.toString ()); assertEquals ("Some (val)", someOption.toString ()); }

לכן, במקום להשתמש בערכי אובייקט ישירות, מומלץ לעטוף אותם בתוך אוֹפְּצִיָה מופע כפי שמוצג לעיל.

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

בקטע השני של החלק הזה היינו זקוקים ל- ריק בדוק, בו נקצה ערך ברירת מחדל למשתנה לפני שננסה להשתמש בו. אוֹפְּצִיָה יכול להתמודד עם זה בשורה אחת, גם אם יש null:

@Test ציבורי בטל givenNull_whenCreatesOption_thenCorrect () {שם מחרוזת = null; Option nameOption = Option.of (שם); assertEquals ("baeldung", nameOption.getOrElse ("baeldung")); }

או שאינו אפס:

@ מבחן הריק ציבורי givenNonNull_whenCreatesOption_thenCorrect () {שם מחרוזת = "baeldung"; Option nameOption = Option.of (שם); assertEquals ("baeldung", nameOption.getOrElse ("notbaeldung")); }

שימו לב איך, בלי ריק בודק, אנו יכולים לקבל ערך או להחזיר ברירת מחדל בשורה אחת.

3. טופל

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

Vavr מביא צינורות לג'אווה 8. צמרות הם מסוג Tuple1, Tuple2 ל Tuple8 תלוי במספר האלמנטים שהם צריכים לקחת.

כרגע יש גבול עליון של שמונה אלמנטים. אנו ניגשים לאלמנטים של כפולה כמו כפולה._n איפה נ דומה לרעיון של אינדקס במערכים:

חלל ציבורי כאשרCreatesTuple_thenCorrect1 () {Tuple2 java8 = Tuple.of ("Java", 8); אלמנט מחרוזת 1 = java8._1; int element2 = java8._2 (); assertEquals ("Java", element1); assertEquals (8, element2); }

שימו לב שהאלמנט הראשון מאוחזר באמצעות n == 1. אז tuple אינו משתמש בבסיס אפס כמו מערך. יש להכריז על סוגי האלמנטים אשר יאוחסנו בכפולת בהצהרת הסוג שלה כמוצג לעיל ולמטה:

@ מבחן ציבורי בטל כאשרCreatesTuple_thenCorrect2 () {Tuple3 java8 = Tuple.of ("Java", 8, 1.8); אלמנט מחרוזת 1 = java8._1; int element2 = java8._2 (); אלמנט כפול = java8._3 (); assertEquals ("Java", element1); assertEquals (8, element2); assertEquals (1.8, element3, 0.1); }

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

4. נסה

ב- Vavr, לְנַסוֹת הוא מיכל לחישובמה שעלול לגרום לחריג.

כפי ש אוֹפְּצִיָה עוטף חפץ בטל כך שלא נצטרך לטפל במפורש אפסים עם אם צ'קים, לְנַסוֹת עוטף חישוב כך שלא נצטרך לדאוג במפורש לחריגים באמצעותו נסה לתפוס חסימות.

קח לדוגמא את הקוד הבא:

@Test (צפוי = ArithmeticException.class) חלל ציבורי givenBadCode_whenThrowsException_thenCorrect () {int i = 1/0; }

לְלֹא נסה לתפוס חוסם, היישום היה קורס. על מנת להימנע מכך, עליכם לעטוף את ההצהרה ב- נסה לתפוס לַחסוֹם. עם Vavr נוכל לעטוף את אותו קוד ב- a לְנַסוֹת למשל ולקבל תוצאה:

@Test הציבור בטל givenBadCode_whenTryHandles_thenCorrect () {נסה תוצאה = Try.of (() -> 1/0); assertTrue (result.isFailure ()); }

אם החישוב הצליח או לא ניתן לבדוק על ידי בחירה בכל נקודה בקוד.

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

@Test הציבור בטל givenBadCode_whenTryHandles_thenCorrect2 () {נסה חישוב = Try.of (() -> 1/0); int errorSentinel = result.getOrElse (-1); assertEquals (-1, errorSentinel); }

או אפילו לזרוק במפורש חריג מבחירתנו:

@Test (צפוי = ArithmeticException.class) חלל ציבורי givenBadCode_whenTryHandles_thenCorrect3 () {נסה תוצאה = Try.of (() -> 1/0); result.getOrElseThrow (ArithmeticException :: new); }

בכל המקרים הנ"ל, יש לנו שליטה על המתרחש לאחר החישוב, הודות ל- Vavr לְנַסוֹת.

5. ממשקים פונקציונליים

עם הגעתו של Java 8, ממשקים פונקציונליים מובנים וקלים יותר לשימוש, במיוחד בשילוב עם lambdas.

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

@ מבט חלל ציבורי שניתןJava8Function_whenWorks_thenCorrect () {כיכר הפונקציה = (num) -> num * num; int result = square.apply (2); assertEquals (4, תוצאה); }

השני לוקח רק שני פרמטרים ומייצר תוצאה:

@ מבחן חלל ציבורי שניתןJava8BiFunction_whenWorks_thenCorrect () {BiFunction sum = (num1, num2) -> num1 + num2; int result = sum.apply (5, 7); assertEquals (12, תוצאה); }

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

ממש כמו טופלס, ממשקים פונקציונליים אלה נקראים על פי מספר הפרמטרים שהם לוקחים: פונקציה 0, פונקציה 1, פונקציה 2 וכו 'עם Vavr היינו כותבים את שתי הפונקציות הנ"ל כך:

@Test הציבור בטל givenVavrFunction_whenWorks_thenCorrect () {פונקציה 1 ריבוע = (num) -> num * num; int result = square.apply (2); assertEquals (4, תוצאה); }

וזה:

@ מבחן חלל ציבורי givenVavrBiFunction_whenWorks_thenCorrect () {Function2 sum = (num1, num2) -> num1 + num2; int result = sum.apply (5, 7); assertEquals (12, תוצאה); }

כשאין פרמטר אך עדיין אנו זקוקים לפלט, ב- Java 8 נצטרך להשתמש ב- צרכן סוג, ב Vavr פונקציה 0 נמצא שם כדי לעזור:

@ מבחן ציבורי בטל כאשרCreatesFunction_thenCorrect0 () {Function0 getClazzName = () -> this.getClass (). GetName (); מחרוזת clazzName = getClazzName.apply (); assertEquals ("com.baeldung.vavr.VavrTest", clazzName); }

מה דעתך על פונקציה של חמישה פרמטרים, זה רק עניין של שימוש פונקציה 5:

@ מבחן ציבורי בטל כאשרCreatesFunction_thenCorrect5 () {פונקציה 5 concat = (a, b, c, d, e) -> a + b + c + d + e; String finalString = concat.apply ("שלום", "עולם", "!", "למד", "Vavr"); assertEquals ("שלום עולם! למד Vavr", finalString); }

אנו יכולים גם לשלב את שיטת המפעל הסטטית פונקציה N. לכל אחת מהפונקציות ליצור פונקציית Vavr מתוך הפניה לשיטה. כאילו אם יש לנו את הדברים הבאים סְכוּם שיטה:

סכום ציבורי אינטל (int a, int b) {החזר a + b; }

אנו יכולים ליצור פונקציה מתוכה כך:

@ מבחן פומבי בטל כאשרCreatesFunctionFromMethodRef_thenCorrect () {Function2 sum = Function2.of (this :: sum); int summed = sum.apply (5, 6); assertEquals (11, סיכמו); }

6. אוספים

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

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

ממשק אוסף {void clear (); }

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

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

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

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

@Test (צפוי = UnsupportedOperationException.class) בטל פומבי כאשרImmutableCollectionThrows_thenCorrect () {java.util.List wordList = Arrays.asList ("abracadabra"); java.util.List list = Collections.unmodifiableList (wordList); list.add ("בום"); }

כל הבעיות שלעיל אינן קיימות באוספי Vavr.

ליצירת רשימה ב- Vavr:

@Test ציבורי בטל כאשרCreateVavrList_thenCorrect () {List intList = List.of (1, 2, 3); assertEquals (3, intList.length ()); assertEquals (מספר שלם חדש (1), intList.get (0)); assertEquals (מספר שלם חדש (2), intList.get (1)); assertEquals (מספר שלם חדש (3), intList.get (2)); }

ממשקי API זמינים לביצוע חישובים ברשימה במקום:

@ מבחן ציבורי בטל כאשר SumSVavrList_thenCorrect () {int sum = List.of (1, 2, 3) .sum (). IntValue (); assertEquals (6, סכום); }

אוספי Vavr מציעים את רוב השיעורים הנפוצים הנמצאים במסגרת Java Collections Framework ולמעשה, כל התכונות מיושמות.

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

סיקור מלא של אוספי Vavr חורג מתחום המאמר.

7. אימות

Vavr מביא את המושג פונקקטור יישום לג'אווה מעולם התכנות הפונקציונלי. במונחים הפשוטים ביותר, an פונקקטור יישום מאפשר לנו לבצע רצף פעולות תוך צבירת התוצאות.

הכיתה vavr.control. אימות מקלה על הצטברות הטעויות. זכור כי, בדרך כלל, תוכנית מסתיימת ברגע שנתקלה בשגיאה.

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

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

אדם בכיתה ציבורית {פרטי שם מחרוזת; גיל פרטי פרטי; // קונסטרוקציות סטנדרטיות, סטרים וגטרים, toString}

לאחר מכן, אנו יוצרים מחלקה בשם PersonValidator. כל שדה יאומת בשיטה אחת וניתן להשתמש בשיטה אחרת לשילוב כל התוצאות לאחת מַתַן תוֹקֵף למשל:

class PersonValidator {String NAME_ERR = "תווים לא חוקיים בשם:"; מחרוזת AGE_ERR = "הגיל חייב להיות לפחות 0"; אימות ציבורי validatePerson (שם מחרוזת, גיל int) {return Validation.combine (validateName (name), validateAge (age)). ap (אדם :: חדש); } אימות פרטי validateName (שם מחרוזת) {מחרוזת invalidChars = name.replaceAll ("[a-zA-Z]", ""); להחזיר invalidChars.isEmpty ()? Validation.valid (name): Validation.invalid (NAME_ERR + invalidChars); } אימות פרטי validateAge (int age) {age return <0? Validation.invalid (AGE_ERR): Validation.valid (גיל); }}

הכלל ל גיל הוא שצריך להיות מספר שלם גדול מ- 0 והכלל עבור שֵׁם היא שהיא לא צריכה להכיל תווים מיוחדים:

@ מבחן ציבורי בטל כאשר ValidationWorks_thenCorrect () {PersonValidator personValidator = PersonValidator חדש (); מַתַן תוֹקֵף valid = personValidator.validatePerson ("ג'ון דו", 30); מַתַן תוֹקֵף invalid = personValidator.validatePerson ("John? Doe! 4", -1); assertEquals ("Valid (Person [name = John Doe, age = 30])", valid.toString ()); assertEquals ("לא חוקי (רשימה (תווים לא חוקיים בשם:?! 4, הגיל חייב להיות לפחות 0))", invalid.toString ()); }

ערך תקף כלול ב- אימות. תקף למשל, רשימה של שגיאות אימות כלולה ב- אימות. לא חוקי למשל. אז כל שיטת אימות חייבת להחזיר אחת מהשתיים.

בְּתוֹך אימות. תקף הוא מופע של אדם בזמן שבפנים אימות. לא חוקי היא רשימה של שגיאות.

8. עצלן

עָצֵל הוא מיכל המייצג ערך המחושב בעצלתיים כלומר החישוב נדחה עד לתוצאה הנדרשת. יתר על כן, הערך המוערך נשמר במטמון או מתוזמן ומוחזר שוב ושוב בכל פעם שהוא נחוץ מבלי לחזור על החישוב:

@Test הציבור בטל givenFunction_whenEvaluatesWithLazy_thenCorrect () {עצלן עצלן = Lazy.of (מתמטיקה :: אקראית); assertFalse (lazy.isEvaluated ()); כפול val1 = lazy.get (); assertTrue (lazy.isEvaluated ()); כפול val2 = lazy.get (); assertEquals (val1, val2, 0.1); }

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

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

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

למרות זאת, עָצֵל שוב מחזיר בעצלתיים את הערך המחושב בתחילה כפי שמאשרת הקביעה הסופית.

9. התאמת תבניות

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

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

@ מבחן ציבורי בטל כאשר IfWorksAsMatcher_thenCorrect () {int קלט = 3; פלט מחרוזת; אם (קלט == 0) {פלט = "אפס"; } אם (קלט == 1) {פלט = "אחד"; } אם (קלט == 2) {פלט = "שניים"; } אם (קלט == 3) {פלט = "שלוש"; } אחר {output = "לא ידוע"; } assertEquals ("שלוש", פלט); }

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

אלטרנטיבה נוספת היא שימוש ב- החלף הַצהָרָה:

@ מבחן ציבורי בטל כאשר SwitchWorksAsMatcher_thenCorrect () קלט int = 2; פלט מחרוזת; מתג (קלט) {מקרה 0: פלט = "אפס"; לשבור; מקרה 1: פלט = "אחד"; לשבור; מקרה 2: פלט = "שניים"; לשבור; מקרה 3: פלט = "שלוש"; לשבור; ברירת מחדל: פלט = "לא ידוע"; לשבור; } assertEquals ("שניים", פלט); }

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

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

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

@Test ציבורי בטל whenMatchworks_thenCorrect () {קלט int = 2; פלט מחרוזת = התאמה (קלט). Of (Case ($ (1), "one"), Case ($ (2), "two"), Case ($ (3), "three"), Case ($ ( ), "?")); assertEquals ("שניים", פלט); }

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

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

התאמה (arg) .of (Case ($ (isIn ("- h", "--help")), o -> run (this :: displayHelp)), Case ($ (isIn ("- v", " --version ")), o -> run (this :: displayVersion)), Case ($ (), o -> run (() -> {throw new IllegalArgumentException (arg);})));

חלק מהמשתמשים עשויים להכיר את גרסת הקיצור (-v) בעוד שאחרים, את הגרסה המלאה (–גרסה). על מעצב טוב לשקול את כל המקרים הללו.

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

10. מסקנה

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

קוד המקור המלא של מאמר זה זמין בפרויקט Github.


$config[zx-auto] not found$config[zx-overlay] not found