Site icon בלוג ארכיטקטורת תוכנה

קוטלין (Kotlin) למפתחי ג'אווה ותיקים – חלק ח': קוטלין וג'אווה (interoperability)

כיצד null יכול לצוץ לו בקוטלין ללא הודעה מוקדמת?

 

כבר כמה פעמים נשאלתי את השאלה: "האם אפשר לרשת מחלקת ג'אווה בקוטלין? קוטלין בג'אווה?"

בוודאי שאפשר! אחרת לא הייתי אומר ש interoperability ביניהן כ"כ מוצלח.

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

ערבוב של קוד קוטלין וג'אווה לצורך רצף הקריאות. במציאות כמובן שהקוד ישב בקבצים נפרדים.
  1. יצרנו מחלקה מופשטת A בשפת ג'אווה.
  2. הרחבנו את המחלקה בג'אווה A – בעזרת מחלקה בקוטלין B.
    1. מכיוון שברירת המחדל בקוטלין היא מחלקה final – עלינו להגדיר אותה כ open ע"מ שקוד הג'אווה יוכל לרשת את המחלקה C.
  3. ואכן הרחבנו את המחלקה בקוטלין B בג'אווה, ללא בעיה. כל שפה שומרת על הקונבנציות שלה (במידת האפשר)
  4. הממ… ה IDE מעיר לי פה משהו: 
Not annotated method overrides method annotated with @NotNull 

מה זה?
אני לא רואה Annotation בשם NotNull@ בקוד.

מה? java.lang.NullPointerException? – אבל אני כותב בקוטלין!?

 

בכדי להבין מה קורה, נחזור שלב אחר אחורה – למחלקה KotlinB.

במחלקה הזו דרסנו את המתודה ()getHelloMessage שהוגדרה בג'אווה.
ערך ההחזרה של המתודה שהוגדרה בג'אווה הוא String, אבל מה זה אומר עבור קוטלין: String או ?String, אולי?

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

והנה השימוש שלה בקוטלין:

ה IDE מסמן לי שערך ההחזרה של המתודה הזו הוא !String.

אין טיפוס כזה בקוטלין, וטעות נפוצה היא להניח ש !String הוא ההיפך מ ?String – כלומר: String שהוא בהכרח לא null.

מה שבאמת ה IDE מנסה לומר לנו הוא שהוא לא יודע אם ה String הוא null או לא. אני חושב שתחביר כמו [?]String היה יכול להיות יותר אינטואיטיבי.

מתכנני שפת קוטלין החלו עם הגישה הדפנסיבית והניחו שערך ההחזרה של מתודה המגיעה מג'אווה הוא null – אבל גישה זו הפכה את הקוד שמשתמש בג'אווה (ובד"כ יש הרבה כזה) – לקוד מסורבל מלא בדיקות בפני null. קוד לא יפה:

other?.java?.object?.always?.might?.be?.null()
 

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

זה נוח, אבל גם יכול לגרום לשגיאות בלתי צפויות.

הנה דוגמה מהחיים:

jdbi הוא פריימוק רזה (וחביב) הכתוב בג'אווה, ומאפשר גישה לבסיס הנתונים.
אופן השימוש בו הוא להגדיר interface או abstract class עם מתודות ומוסיף להן annotation עם השאילתה שיש לממש.
jdbi, בעזרת reflection, מג'נרט (בזמן ריצה) אובייקט DAO שמממש את הממשק שהגדרתי. התוצאה היא אובייקט שמבצע את השאילות שהגדרתי ב annotations. קוד עובד.

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

…עד הרגע שאני מפעיל את השאילתה עם job_id שלא קיים – וחוזר לי null.
מתישהו אני "חוטף" NullPointerException.

"מאיפה הגיע לפה null? הגדרתי במפורש שערך ההחזרה של הפונקציה הוא מסוג String – לא ?String.
"וואו, נראה לי שמצאתי באג בקוטלין" הוא שלב הכחשה אנושי…

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

מה שצריך לעשות הוא להגדיר בצורה נכונה את ערך ההחזרה של הפונקציה. קוד הפונקציה, להזכיר, ממומש בג'אווה.
במקרה הזה הוא גם מג'ונרט בזמן ריצה – היכן שהקופיילר כבר סיים את עבודתו – אבל עצם הבעיה הוא חוסר ההבחנה של ג'אווה בין nullable string ל not-null String.

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

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

JebBrains (החברה מאוחרי קוטלין ו IntelliJ) סיפקה annotations לג'אווה שיכולים להנחות את ה IDE, מתי צפוי null ומתי לא ייתכן null. הנה דוגמה:

השימוש ב annotation מסיר מה IDE את הספק:

ואז הוא יכול להגן עלי.

אין כנראה פתרון טוב יותר: ואישהו בתפר בין ג'אווה וקוטלין עלולים "לזלוג" nulls מג'אווה לקוטלין.
סימון ה Nullability בעזרת annotations הוא לא תמיד אפשרי (למשל: ספריית צד-שלישי) וגם אותו אפשר לשכוח.

באופן דומה, אגב:

(Mutable)List

הוא סימן שה IDE מספק שמשמעו: רשימה שייתכן שהיא mutable, וייתכן immutable. הקומפיילר לא מסוגל להגיע למסקנה בעצמו.

הנה דוגמה לביטוי מורכב:

קוד הג'אווה מאחורי המתודה ()getStrings הוא זה:
 
 
מה שמוביל אותנו לעניין נוסף שכדאי להכיר:
כאשר מתודה בג'אווה נקראת ב naming של JavaBeans, כלומר: ()getXxxx או ()setXxxx – קוטלין מתייחסת אליהם כתכונה בשם xxxx.
 
הנה הקוד בקוטלין שקורא לקוד הג'אווה הנ"ל:
 
 
אתם רואים שהשלפנים (getters) שכתובים בג'אווה נראים בקוטלין כמו תכונות לכל דבר.
מכיוון ש true היא מילה שמורה בקוטלין, יש לעטוף (escaping) אותה בגרש מוטה.
 
באופן סימטרי, תכונות (properties) שהוגדרו בקוטלין כ yyyy יופיעו בקוד הג'אווה כמתודות ()getYyyy ו/או ()setYyyy.
 
 
כדרך אגב, יכולת ה escaping של שמות בקוטלין – מאפשר לתת שמות קריאים יותר לפונקציות של בדיקות:
 
 

מתודות המכילות רווחים לא יוכלו להיקרא ע"י קוד ג'אווה, אך ספציפית בבדיקות (JUnit) – זיהוי המתודות נעשה ע"י reflection ולא ע"פ שם הפונקציה – ולכן אין בעיה.

 

חשיפה מתוכננת

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

אם יש לי תכונה בשם yyyy בקוטלין (מה שטבעי בקוטלין), הגישה אליה תהיה בעזרת getYyyy ו setYyyy – מה שטבעי בג'אווה.

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

אאוץ. אאוץ!!

הנה רשימת בעיות:

מה עושים?

הנה הפתרון, מבוסס annotations – אך עובד:

 

JvmOverloads היא הנחיה להשתמש ב default values על מנת לג'נרט מופעים שונים של פונקציות בקומבינציות השונות, מה שנקרא בג'אווה Method Overloading. אני מניח ששאר ה annotations הן self-explanatory.

הוספתי גם דוגמה לשימוש ב extension function. איך משתמשים ב extension functions מתוך ג'אווה?!

הנה קוד הג'אווה שמשתמש בקוד הקוטלין, "בהנאה":

 

דיי אלגנטי, מלבד ה Extension Function שבאמת לא משתווה לשימוש בקוטלין.
יש לציין את שם המחלקה (כל קובץ של קוטלין מתתרגם למחלקה עם Kt בסופה) נקודה שם המתודה – כאשר הפרמטר הראשון הוא האובייקט עליו אנו רוצים לפעול.

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

 

סיכום

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

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

ה interoperability בין ג'אווה וקוטלין פשוט עובד!

יצא לראות לא מעט קוד קוטלין (צד-שרת) שעובד:

שיהיה בהצלחה!

Exit mobile version