ביטויים למבדה וממשקים פונקציונליים: טיפים ושיטות עבודה מומלצות

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

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

2. העדיפו ממשקים פונקציונליים סטנדרטיים

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

שקול ממשק פו:

ממשק ציבורי @FunctionalInterface Foo {שיטת מחרוזת (מחרוזת מחרוזת); }

ושיטה לְהוֹסִיף() בכיתה כלשהי UseFoo, שלוקח ממשק זה כפרמטר:

public מחרוזת להוסיף (מחרוזת מחרוזת, Foo foo) {להחזיר foo.method (מחרוזת); }

לביצועו היית כותב:

Foo foo = parameter -> parameter + "from lambda"; תוצאת מחרוזת = useFoo.add ("הודעה", foo);

תסתכל מקרוב ותראה את זה פו אינה אלא פונקציה המקבלת טיעון אחד ומייצרת תוצאה. Java 8 כבר מספקת ממשק כזה ב- פוּנקצִיָה מחבילת java.util.function.

עכשיו אנחנו יכולים להסיר ממשק פו לשנות את הקוד שלנו ל:

הוסף מחרוזת ציבורית (מחרוזת מחרוזת, פונקציה fn) {להחזיר fn.apply (מחרוזת); }

כדי לבצע זאת, נוכל לכתוב:

פונקציה fn = פרמטר -> פרמטר + "ממבדה"; תוצאת מחרוזת = useFoo.add ("הודעה", fn);

3. השתמש ב- @FunctionalInterface ביאור

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

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

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

אז השתמש בזה:

ממשק ציבורי @FunctionalInterface Foo {שיטת מחרוזת (); }

במקום רק:

ממשק ציבורי Foo {שיטת מחרוזת (); }

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

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

ממשק ציבורי @FunctionalInterface Foo {שיטת מחרוזת (מחרוזת מחרוזת); ברירת מחדל בטל defaultMethod () {}}

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

לדוגמה:

ממשק ציבורי @FunctionalInterface FooExtended מרחיב Baz, Bar {} @FunctionalInterface ממשק ציבורי Baz {שיטת מחרוזת (מחרוזת מחרוזת); מחרוזת ברירת מחדל defaultBaz () {}} @FunctionalInterface סרגל הממשק הציבורי {שיטת מחרוזת (מחרוזת מחרוזת); ברירת מחדל מחרוזת defaultBar () {}}

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

לדוגמה, בואו נוסיף את ה- defaultCommon () שיטה ל בָּר ו בז ממשקים:

ממשק ציבורי @FunctionalInterface Baz {שיטת מחרוזת (מחרוזת מחרוזת); default מחרוזת defaultBaz () {} default מחרוזת defaultCommon () {}} @FunctionalInterface סרגל הממשק הציבורי {שיטת מחרוזת (מחרוזת מחרוזת); default מחרוזת defaultBar () {} default מחרוזת defaultCommon () {}}

במקרה זה, נקבל שגיאת זמן הידור:

ממשק FooExtended יורש ברירות מחדל לא קשורות ל- defaultCommon () מהסוגים Baz ו- Bar ...

כדי לתקן זאת, defaultCommon () יש לבטל את השיטה ב- FooExtended מִמְשָׁק. אנו יכולים כמובן לספק יישום מותאם אישית של שיטה זו. למרות זאת, אנו יכולים גם לעשות שימוש חוזר ביישום מממשק האב:

ממשק ציבורי @FunctionalInterface FooExtended מרחיב Baz, Bar {@Override ברירת מחדל מחרוזת defaultCommon () {להחזיר Bar.super.defaultCommon (); }}

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

5. מיידיות ממשקים פונקציונליים עם ביטויים למבדה

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

Foo foo = פרמטר -> פרמטר + "מ- Foo";

על פני מעמד פנימי:

Foo fooByIC = חדש Foo () {@ שיטה מחרוזת ציבורית ציבורית (מחרוזת מחרוזת) {מחרוזת החזרה + "מ- Foo"; }}; 

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

6. הימנע משיטות עומס עם ממשקים פונקציונליים כפרמטרים

השתמש בשיטות עם שמות שונים כדי למנוע התנגשויות; בואו נסתכל על דוגמה:

מעבד ממשק ציבורי {תהליך מחרוזת (c ניתן להתקשר) זורק חריג; תהליך מחרוזת (ספקים); } ProcessorImpl בכיתה ציבורית מיישם את המעבד {@Override Public String Process (Callable c) זורק חריג {// פרטי יישום} @Override Public String Process (ספקי) {// פרטי הטמעה}}

במבט ראשון זה נראה סביר. אבל כל ניסיון להוציא לפועל את אחד מאלה ProcessorImplשיטות:

תוצאת מחרוזת = processor.process (() -> "abc");

מסתיים בשגיאה עם ההודעה הבאה:

התייחסות לתהליך מעורפלת הן בשיטת התהליך (java.util.concurrent.Callable) ב- com.baeldung.java8.lambda.tips.ProcessorImpl ותהליך השיטה (java.util.function.Supplier) ב- com.baeldung.java8.lambda. טיפים. התאמה למעבד

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

Process StringWithCallable (Callable c) זורק חריג; תהליך מחרוזת WithSupplier (ספקים);

השנייה היא ביצוע ליהוק ידני. זה לא מועדף.

תוצאת מחרוזת = processor.process ((ספק) () -> "abc");

7. אל תתייחס לביטויי למבדה כאל שיעורים פנימיים

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

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

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

למשל, בכיתה UseFoo יש לך משתנה מופע ערך:

ערך מחרוזת פרטי = "סוגרת ערך היקף";

ואז בשיטה כלשהי של מחלקה זו הצב את הקוד הבא וביצע שיטה זו.

public String scopeExperiment () {Foo fooIC = new Foo () {String value = "ערך מחלקה פנימית"; @ שיטה מחרוזת ציבורית ציבורית (מחרוזת מחרוזת) {להחזיר this.value; }}; מחרוזת resultIC = fooIC.method (""); Foo fooLambda = פרמטר -> {String value = "Lambda value"; להחזיר this.value; }; מחרוזת resultLambda = fooLambda.method (""); להחזיר "תוצאות: resultIC =" + resultIC + ", resultLambda =" + resultLambda; }

אם אתה מבצע את scopeExperiment () השיטה, תקבל את התוצאה הבאה: תוצאות: resultIC = ערך מחלקה פנימי, resultLambda = סוגרת ערך היקף

כפי שאתה יכול לראות, על ידי התקשרות ערך זה ב- IC, אתה יכול לגשת למשתנה מקומי מההופעה שלו. אבל במקרה של למבדה, ערך זה שיחה נותנת לך גישה למשתנה ערך שמוגדר ב- UseFoo class, אבל לא למשתנה ערך מוגדר בתוך גופה של הלמדה.

8. שמור על ביטויים למבדה קצרים ומובנים מאליהם

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

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

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

8.1. הימנע מגושי קוד בגוף למבה

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

אם יש לך גוש קוד גדול, הפונקציונליות של ה- lambda אינה ברורה מיד.

עם זאת בחשבון, בצע את הפעולות הבאות:

Foo foo = פרמטר -> buildString (פרמטר);
פרטי מחרוזת buildString (פרמטר מחרוזת) {מחרוזת תוצאה = "משהו" + פרמטר; // שורות רבות של קוד להחזרת התוצאה; }

במקום:

Foo foo = parameter -> {String result = "Something" + parameter; // שורות רבות של קוד להחזרת התוצאה; };

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

8.2. הימנע מלציין סוגי פרמטרים

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

תעשה את זה:

(a, b) -> a.toLowerCase () + b.toLowerCase ();

במקום זה:

(מחרוזת a, מחרוזת b) -> a.toLowerCase () + b.toLowerCase ();

8.3. הימנע מסוגריים סביב פרמטר יחיד

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

אז, עשה זאת:

a -> a.toLowerCase ();

במקום זה:

(א) -> a.toLowerCase ();

8.4. הימנע מהצהרת החזרה וסוגרים

פלטה ו לַחֲזוֹר הצהרות הן אופציונליות בגופי למבדה בשורה אחת. המשמעות היא שניתן להשמיט אותם לשם בהירות ותמציתיות.

תעשה את זה:

a -> a.toLowerCase ();

במקום זה:

a -> {להחזיר a.toLowerCase ()};

8.5. השתמש בהפניות לשיטה

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

אז הביטוי למבדה:

a -> a.toLowerCase ();

יכול להיות מוחלף על ידי:

מחרוזת :: toLowerCase;

זה לא תמיד קצר יותר, אבל זה הופך את הקוד לקריא יותר.

9. השתמש במשתנים "אפקטיביים סופיים"

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

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

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

לדוגמה, הקוד הבא לא יתכנס:

שיטת הריק הציבורי () {String localVariable = "Local"; Foo foo = פרמטר -> {String localVariable = פרמטר; החזר מקומי משתנה; }; }

המהדר יודיע לך כי:

המשתנה 'localVariable' כבר מוגדר בהיקף.

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

10. הגן על משתני אובייקט מפני מוטציה

אחת המטרות העיקריות של lambdas היא שימוש במחשוב מקביל - מה שאומר שהם באמת מועילים בכל מה שקשור לבטיחות פתילים.

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

שקול את הקוד הבא:

int [] סה"כ = int int [1]; ניתן להפעיל r = () -> סה"כ [0] ++; r.run ();

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

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

11. מסקנה

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

השלם קוד מקור לדוגמא זמין בפרויקט GitHub זה - זהו פרויקט Maven ו- Eclipse, כך שניתן לייבא אותו ולהשתמש בו כמות שהוא.


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