חריגים ב- Java 8 Lambda Expressions

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

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

במאמר זה נחקור כמה דרכים להתמודד עם חריגים בעת כתיבת ביטויים למבדה.

2. טיפול בחריגים לא מסומנים

ראשית, בואו נבין את הבעיה בדוגמה.

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

רשימת מספרים שלמים = Arrays.asList (3, 9, 7, 6, 10, 20); מספרים שלמים.forEach (i -> System.out.println (50 / i));

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

רשימת מספרים שלמים = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {try {System.out.println (50 / i);} catch (ArithmeticException e) {System.err.println ("חריג אריתמטי התרחש:" + e.getMessage ());}} );

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

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

lambdaWrapper הצרכן הסטטי (צרכן הצרכן) {החזר i -> {נסה {consumer.accept (i); } לתפוס (ArithmeticException e) {System.err.println ("חריג אריתמטי התרחש:" + e.getMessage ()); }}; }
רשימת מספרים שלמים = Arrays.asList (3, 9, 7, 0, 10, 20); מספרים שלמים.forEach (lambdaWrapper (i -> System.out.println (50 / i)));

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

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

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

סטטי Consumer ConsumerWrapper (Consumer Consumer, Class class) {return i -> {try {consumer.accept (i); } לתפוס (Exception ex) {try {E exCast = clazz.cast (ex); System.err.println ("אירעה חריגה:" + exCast.getMessage ()); } לתפוס (ClassCastException ccEx) {לזרוק לשעבר; }}}; }
רשימת מספרים שלמים = Arrays.asList (3, 9, 7, 0, 10, 20); מספרים שלמים.forEach (consumerWrapper (i -> System.out.println (50 / i), ArithmeticException.class));

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

שימו לב גם ששינינו את שם השיטה מ- lambdaWrapper ל הצרכן עוטף. זה בגלל ששיטה זו מטפלת רק בביטויים למבדה עבור ממשק פונקציונלי מהסוג צרכן. אנו יכולים לכתוב שיטות עטיפה דומות לממשקים פונקציונליים אחרים כמו פוּנקצִיָה, BiFunction, BiConsumer וכולי.

3. טיפול בחריגים בדוקים

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

סטטי ריק ריק writeToFile (מספר שלם שלם) זורק IOException {// לוגיקה לכתוב לקובץ אשר זורק IOException}

שים לב שהשיטה לעיל עשויה לזרוק את IOException.

רשימת מספרים שלמים = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> writeToFile (i));

בקומפילציה אנו מקבלים את השגיאה:

java.lang.Error: בעיית אוסף לא פתורה: סוג חריג לא מטופל IOException

כי IOException הוא חריג מסומן, עלינו לטפל בו במפורש. יש לנו שתי אפשרויות.

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

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

בואו נבדוק את שתי האפשרויות.

3.1. זורק חריג מסומן מביטויי למבדה

בואו נראה מה יקרה כשאנחנו מכריזים על IOException על רָאשִׁי שיטה:

סטטי ציבורי ריק ריק (String [] args) זורק IOException {רשימה שלמה = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> writeToFile (i)); }

עוֹד, אנו מקבלים את אותה שגיאה של לא מטופל IOException במהלך האוסף.

java.lang.Error: בעיית אוסף לא פתורה: סוג חריג לא מטופל IOException

הסיבה לכך היא שהבעות למבדה דומות לכיתות פנימיות אנונימיות.

במקרה שלנו, writeToFile השיטה היא יישום של צרכן ממשק פונקציונלי.

בואו נסתכל על ה- צרכןההגדרה:

ממשק ציבורי @FunctionalInterface הצרכן {void accept (T t); }

כמו שאנו יכולים לראות לְקַבֵּל השיטה אינה מצהירה על חריג מסומן. זו הסיבה לכך writeToFile אסור לזרוק את IOException.

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

רשימת מספרים שלמים = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {try {writeToFile (i);} לתפוס (IOException e) {לזרוק RuntimeException (e);}}); 

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

אנחנו יכולים להשתפר מזה.

בואו ניצור ממשק פונקציונלי מותאם אישית עם יחיד לְקַבֵּל שיטה שזורקת חריג.

ממשק ציבורי @FunctionalInterface ThrowingConsumer {void accept (T t) זורק E; }

ועכשיו, בואו נשתמש בשיטת עטיפה שתוכל להחזיר את החריג:

סטטי צרכני לזרוק ConsumerWrapper (ThrowingConsumer throwingConsumer) {החזר i -> {נסה {throwingConsumer.accept (i); } לתפוס (Exception ex) {לזרוק RuntimeException חדש (ex); }}; }

לבסוף, אנו מסוגלים לפשט את אופן השימוש שלנו ב- writeToFile שיטה:

רשימת מספרים שלמים = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (throwingConsumerWrapper (i -> writeToFile (i)));

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

שניהם, ה ThrowingConsumer וה throwingConsumerWrapper הם כלליים וניתנים לשימוש חוזר במקומות שונים של היישום שלנו.

3.2. טיפול בחריג בדוק בביטוי למבדה

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

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

טיפול צרכני סטטי ConsumerWrapper (ThrowingConsumer throwingConsumer, Class exceptionClass) {return i -> {try {throwingConsumer.accept (i); } תפוס (Exception ex) {try {E exCast = exceptionClass.cast (ex); System.err.println ("אירעה חריגה:" + exCast.getMessage ()); } לתפוס (ClassCastException ccEx) {לזרוק RuntimeException חדש (לשעבר); }}}; }

בואו נראה כיצד להשתמש בו בפועל:

רשימת מספרים שלמים = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (handlingConsumerWrapper (i -> writeToFile (i), IOException.class));

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

4. מסקנה

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

דרך נוספת תהיה לחקור את גרזן הזריקות.

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

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


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