הבעיה ש-Optional פותר
למתודה שייתכן שאין לה תשובה תמיד הייתה ב-Java בחירה מביכה: להחזיר null ולקוות שהקורא יזכור לבדוק. בדרך כלל הוא לא זוכר, והתוצאה היא NullPointerException שצצה רחוק מהמתודה שהחזירה את ה-null.
Optional<T> הוא קופסה קטנה שמכילה ערך או שהיא ריקה באופן מפורש. כשמתודה מחזירה Optional<User> במקום User, היא אומרת לקוראים "ייתכן שלא אמצא כלום" ישירות בחתימה שלה, והמהדר מכוון אותם לטפל במקרה הזה.
יצירת Optional
יש שלוש מתודות יצירה, ובחירת הנכונה חשובה:
Optional.of(value): הערך חייב להיות לא null, אחרת היא זורקתNullPointerExceptionבמקום.Optional.ofNullable(value): ריק אם הערך הוא null, עם ערך אחרת. השתמשו בה כדי לעטוף משהו שעשוי להיות null.Optional.empty(): Optional ריק.
טעות נפוצה היא להשתמש ב-Optional.of(x) על ערך שעלול להיות null: זה מבטל את כל המטרה וזורק בדיוק את החריגה שניסיתם להימנע ממנה. כשיש ספק, השתמשו ב-ofNullable.
קריאת הערך בבטחה
אחרי שיש Optional, המטרה היא להוציא את הערך בלי להניח שהוא שם. הכלי הגס הוא get(), וכמעט אף פעם לא כדאי להשתמש בו:
Optional<String> name = Optional.empty();
String value = name.get(); // זורק NoSuchElementException: פשוט NPE בתחפושת
במקום זה, ספקו ערך חלופי או הגיבו לקיום הערך. orElse מחזירה ברירת מחדל כשהוא ריק; ifPresent מריצה קוד רק כשיש ערך:
השתמשו ב-orElseGet(supplier) במקום orElse כשבניית ברירת המחדל יקרה: ה-supplier רץ רק אם ה-Optional באמת ריק. הארגומנט של orElse תמיד מחושב, גם כשאין בו צורך.
orElseThrow עבור "זה אמור להיות קיים"
לפעמים ריק הוא באמת שגיאה: ערך הגדרה חובה, משתמש שאמור להיות במסד הנתונים. orElseThrow הופכת Optional ריק לחריגה ברורה לבחירתכם:
זה נקרא הרבה יותר טוב מבלוק if (opt.isPresent()), והופך את הכישלון למפורש בנקודה שבה הוא קורה.
המרה עם map ו-filter
התמורה האמיתית היא בשרשור. map מפעילה פונקציה על הערך רק אם הוא קיים ומשאירה Optional ריק ריק, כך שממירים בלי לגעת אף פעם ב-null. filter משמיטה את הערך אם הוא לא עובר בדיקה.
אם פונקציית ההמרה עצמה מחזירה Optional, השתמשו ב-flatMap במקום map כדי להימנע מ-Optional<Optional<T>> מסורבל. זה משקף את ההבחנה בין map ל-flatMap שראיתם ב-streams: Optional מתנהג כמו stream של אפס או איבר אחד.
איפה Optional מתאים (ואיפה לא)
Optional תוכנן כטיפוס החזרה של מתודות שעשויות באופן לגיטימי לא להפיק תוצאה: הדוגמאות הקלאסיות הן Stream.findFirst(), חיפושים בסגנון Map ופענוח קלט. השתמשו בו שם, וה-API שלכם יתעד בעצמו את החורים שלו.
הוא לא נועד ל:
- שדות. הוא לא ניתן לסריאליזציה ומוסיף אובייקט לכל שדה. השתמשו בהפניות רגילות ובדקו אותן בבנאי.
- פרמטרים של מתודות. הקוראים יצטרכו לעטוף ארגומנטים ב-
Optional.of(...), וזה רועש יותר מהעמסה או מפרמטר שיכול להיות null. - אוספים. החזירו
Listריקה, לאOptional<List>. אוסף ריק כבר אומר "כלום".
כשמשתמשים בו כטיפוס החזרה, Optional הופך באגים שקטים של null לתזכורות בזמן קומפילציה לטפל במקרה החסר.
הבא: חריגות
Optional מטפל בצורה נקייה במקרה היומיומי של "ייתכן שאין ערך", אבל חלק מהכשלים חריגים באמת: קובץ שלא נפתח, רשת שנופלת, קלט שאי אפשר לפענח. Java מייצגת אותם בעזרת חריגות (exceptions), וזה העמוד הבא.
שאלות נפוצות
מה זה Optional ב-Java ולמה להשתמש בו?
Optional<T> הוא מיכל שמחזיק ערך או שהוא ריק. הוא קיים כדי להפוך את "ייתכן שאין ערך" למפורש בטיפוס ההחזרה של מתודה, כך שהקוראים מקבלים דחיפה לטפל במקרה הריק במקום לשכוח בדיקת null ולקבל NullPointerException בזמן ריצה. השתמשו בו בעיקר כטיפוס החזרה של מתודות שעשויות לא להפיק תוצאה, כמו חיפוש שלא מוצא כלום.
מה ההבדל בין Optional.of ל-Optional.ofNullable?
Optional.of(x) זורקת NullPointerException מיד אם x הוא null: השתמשו בה כשידוע שהערך אינו null. Optional.ofNullable(x) מחזירה Optional ריק כש-x הוא null, ו-Optional עם ערך אחרת: השתמשו בה כשהערך עשוי להיות null. Optional.empty() נותנת Optional ריק ישירות.
האם כדאי לקרוא ל-Optional.get() כדי לקרוא את הערך?
הימנעו מ-get() חשוף: הוא זורק NoSuchElementException אם ה-Optional ריק, וזו בסך הכול NullPointerException בשם אחר. העדיפו orElse, orElseGet, orElseThrow, ifPresent או map, כדי שהמקרה הריק תמיד יטופל. אם חייבים לבדוק קודם, הגנו עם isPresent(), אבל המתודות הפונקציונליות נקיות יותר.