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

הגדרת מחלקה פשוטה

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

פשוט, לא?
האמת שזה דיי דומה למחלקה ריקה בג'אווה: פשוט אם אין body – אז אין צורך בסוגריים מסולסלים.

מה השימוש במחלקה ריקה?

לא הרבה.
אפשר לייצר ככה custom exception או Marker interface או Marker Annotation (בתחביר מקוצר הרבה מג'אווה).

טוב, עכשיו נתחיל יותר ברצינות:

  1. בקוטלין לא ניתן להגדיר שדות (fields) למחלקה. במקום זאת – מגדירים תכונות (properties). מה זה אומר?
    1. השורה שלנו תגרום לקומפיילר לייצר גם שדה, וגם accessor methods לשדה הזה.
      1. כאשר כותבים  בקוטלין – הגישה לתכונה (property) תעשה בעזרת person.age – כאילו זהו field שהוא פשוט public.
      2. כאשר כותבים בג'אווה – הגישה לתכונה של מחלקה הכתובה בקוטלין תעשה בעזרת ()person.getAge  ו ()person.setAge – כמקובל.
    2. מאחורי כל תכונה (property) באמת קיים שדה (field). לעתים – לא יהיה בשדה הזה בכלל שימוש. נראה דוגמה בהמשך.
    3. הסיבה מאחורי הוספת properties לשפה, היא כנראה Item 14 בספר "Effective Java" – המזהיר בפני חשיפת שדות שאז לא יהיה ניתן לשנות, לבקר, ולהגביל את הגישה אליהם. "אחרי שנחשפו – זהו!… התלות קיימת וזו יכולה להיות עבודה קשה להסיר אותה…"
    4. בקוטלין נתנו לנו את האפשרות להוסיף custom getter/setter בכל יום שנצטרך – אבל בלי הסרבול של לכתוב getters / setters בעצמנו, בכל פעם, כי "אולי בעתיד יהיה צורך".
      להיות עם – ולהרגיש בלי.
  2. בקוטלין יש primary constructor, וייתכן שיהיו secondary constructors.
    ה primary constructor מוגדר באותה שורה עם הגדרת המחלקה.

    1. מגדירים אותו בעזרת המילה השמורה constructor.
    2. מגדירים אלו פרמטרים הוא יקבל.
    3. אם לא מגדירים primary constructor אזי נוצר default primary constructor – ללא פרמטרים.
  3. ה init block נקרא בכל יצירה של instance של המחלקה, והוא בעצם משמש כגוף הפונקציה של הבנאי הראשי (primary constructor) – אם צריך כזה.
    1. כל התכונות של המחלקה צריכות להיות מאותחלות. אם נסיר את השורה "age = 0" – נקבל שגיאת קומפילציה.
      הקומפיילר דורש שאנו נאתחל את התכונות באותה השורה (מה שנקרא initializer, כמו ב name) – או שנאתחל אותם ב init block / בבנאי הראשי.
  4. הנה הגדרה של בנאי משני למחלקה.
    1. ההגדרה שלו נעשית בעזרת המילה השמורה constructor.
    2. מגדירים את הפרמטרים של הבנאי המשני.
    3. חובה להפעיל את הבנאי הראשי:
      1. או ישירות – ע"י קריאה עם הפרמטרים המתאימים.
      2. או ע"י קריאה לבנאי משני אחר – שהוא יפעיל את הבנאי הראשי.
      3. כאשר קיים default primary constructor – אין צורך ב ()this :.
  5. הנה יצירת מופע של המחלקה בעזרת שימוש בבנאי הראשי.
  6. הנה יצירת מופע של המחלקה – בעזרת שימוש בבנאי המשני.
  7. הנה קריאה לתכונה – היא באמת נראית כמו קריאה לשדה בג'אווה – מה שהופך את הקוד למעט יותר "נקי בעין".
יש לא מעט מידע בפסקה הזו – אולי תרצו לעבור עליה שוב.
אתם בוודאי רואים שיש פה השפעה לא מעטה #C וגם קצת מסקאלה.
האמת, שיש דרך פשוטה יותר לכתוב את המחלקה Person. הנה היא לפניכם:
  1. הצלחנו לפשט את המחלקה דרמטית, בעזרת הגדרה "נבונה" יותר של הבנאי הראשי.
    1. המילה constructor בשורת המחלקה, לתיאור הבנאי הראשי – היא אופציונלית, ואפשר לוותר עליה.
    2. אם מגדירים val או var על הפרמטרים של הבנאי הראשי – הרי זה שקול להגדרת תכונות בגוף המחלקה. הארגומנטים שנשלחו לבנאי – יאתחלו את התכונות הללו. קצר ונוח.
      יכולת זו שמורה לבנאי הראשי בלבד.
    3. השימוש ב default value בחתימה של הבנאי הראשי – ייתרה את הצורך בבנאי משני.
      1. במקרים לא מעטים, השימוש ב default values – יכול לחסוך שימוש ב Builder Pattern, ולחסוך לא-מעט קוד.
  2. הנה ההרצה: אין שום שינוי, כי לא היה שום שינוי. הקוד שקול לחלוטין.

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

עוד קצת על תכונות (Properties)

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

  1. הגדרנו למחלקה תכונה בשם age – ודרסנו את ה getter. ממש דומה לתחביר של #C.
    1. כעקרון, כאשר דורסים רק שלפן (accessor) אחד, במקרה שלנו: getter, השלפן השני – עדיין קיים במימוש ברירת המחדל שלו. כלומר: ל age עדיין יש default setter.
    2. ספציפית במקרה שלנו, מכיוון ש age הוגדר כ val (כלומר "immutable") – לא ייווצר setter.
  2. רק לצורך ההדגמה יצרתי תכונה מאוד דומה, אותה הגדרנו כ var. במקרה כזה, אנחנו מחויבים לאתחל את השדה של התכונה – גם אם לא יעשה בו שימוש לעולם (במקרה הזה: עם הערך אפס).
  3. כאשר ה getter שלי הוא פשוט – אני יכול להשתמש בתחביר shorthand, בעזרת הסימן =.
    1. יכולתי להשתמש בתחביר shorthand גם עבור age.
  4. אם אני לא רוצה שיגשו ל setter של התכונה – אני פשוט אגדיר אותה כ private.
    התחביר הזה אומר לקומפיילר: צור default setter – אך עשה אותו private.
  5. הנה – סתם בשביל ההדגמה: אני יכול להשתמש ב setter של התכונה בתוך המחלקה.
  6. אגדיר תכונה נוספת בשם nickname – אין לי בעיה לאתחל אותה בערך שנגזר מתכונה אחרת – name.
  7. כאן מימשתי setter לדוגמה. ה getter ה default-י עדיין קיים וזמין.
  8. מה קורה כאשר אנחנו רוצים, ב customer accessor שלנו לגשת לשדה שמאחורי התכונה?
    1. עושים את זה בעזרת המילה field.
    2. field היא מה שנקרא soft keyword, כלומר: keyword שקיים רק ב context מסוים. ממש כמו it – שראינו בפוסט הקודם.
  9. בניסיון ההרצה, שלושת השורות הללו יגרמו ל compilation error.
    1. את לשלושת התכונות הללו: age, name, ו ageNextYear – לא ניתן לקבוע ערך מחוץ למחלקה.
הנה תוצאת ההרצה:

Visibility Modifiers

בקוטלין, visibility ברירת המחדל היא תמיד public. יש שוני קטן בהגדרות כאשר ה visibility modifier מוגדר:

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

הנה ההגדרות:

  • private 
    • יתנהג כמו ג'אווה בתוך מחלקה
    • יגביל גישה לאותו קובץ בלבד, אם השתמשו בו ב top-level.
  • protected
    • יתנהג כמו ג'אווה בתוך מחלקה
    • לא ניתן להשתמש בו ב top-level.
  • internal
    • היא נראות חדשה לקוטלין, שמגדירה אפשרות לגשת לכל מי שנמצא באותו המודול.
      • כיצד מוגדר מודול? ע"י תהליך הקומפליציה: קבצים שקומפלו ביחד.
        זה יכול להיות מודול של Maven, של Gradle, או מודול ב IntelliJ – למשל.
    • היא מתנהגת אותו הדבר בתוך המחלקה, וב top-level

בואו נראה קצת קוד:

  1. הגדרתי את המשתנה כ private – לא יוכלו לגשת אליו מקובץ אחר.
  2. הגדרתי מחלקה שהיא internal – זמינה רק בתוך ה module.
    1. כל המחלקות בקוטלין הן final – לא ניתן לרשת מהן, אלא אם מרשים זאת במפורש בעזרת השימוש ב open. אהבתי!
  3. מה עושים כאשר רוצים להגדיר "שדה פנימי" בקוטלין? רק לשימוש המחלקה?
    – מגדירים תכונה שהיא private, ומשתמשים בה.
  4. פה נראה שאולי עשיתי איזה טריק: הגדרתי getter ו setter על התוכנה – כך שאי אפשר לגשת ל accessors שלה. האם הפכתי אותה ל private?
    1. בפועל: אין תחביר כזה:
    2. הקומפיילר מתעלם מ get ומגדיר רק את ה default setter כ private.
    3. תראו למטה – אמנם הקוד מתקמפל, אבל אני בהחלט יכול לגשת ל someHiddenField ממחלקה אחרת. אופס!
  5. הנה דוגמה תקינה ל default setter שהוא private. אפשר כמובן גם להגדיר custom setter באותו האופן.
  6. ניסיתי להגדיר internal getter – אך קיבלתי שגיאת קומפילציה: ה getter של תכונות חייב להיות באותה נראות כמו התכונה עצמה. חשבו אילו בעיות יכלו להיות אם לא…
  7. באופן הבא אני יכול להגדיר נראות (private) על primary constructor.
    כאשר אני משתמש ב visibility modifier על הבנאי – אני חייב להשתמש במילה constructor.
  8. על השורה הזו אני מקבל warning בקומפליציה: ה class לא הוגדר כ open – אז לא ניתן לרשת ממנו. אין טעם או משמעות להגדיר נראות של protected. צודק הקומפיילר.
נמשיך הלאה, ל"סוגים מיוחדים" של מחלקות בקוטלין.

Data Class

אחד הבזבוזים הגדולים של boilerplate code בג'אווה הוא ביצירה של data classes – מחלקות שכל תפקידן להחזיק כמות מסוימת של נתונים.

אני תמיד הייתי משתמש ב public fields, אבל יש כאלו שהקפידו על getters ו setters, וגם hashCode ו equals וכו'… הרבה קוד – עבור משהו מאוד סטנדרטי.

בקוטלין אפשר להגדיר data class, מחלקה שמספקת לנו:

  • תיאור כוונות ברור: המילה data מצביעה בבירור על הכוונה מאחורי המחלקה.
  • מימוש ל ()equals(), hashCode(), clone, ומימוש default-י וסביר לחלוטין של ()toString.
    בערך כל מה שמחלקה כזו צריכה.
  1. כך מגדירים data class, שהיא מחלקה לכל דבר. את התכונות של ה dataclass הגדרתי בתוך הבנאי הראשי – כמו שהראנו קודם. זה הכי נוח.
  2. מכיוון שזו מחלקה לכל דבר, אני יכול להגדיר לה member function. כמובן שבד"כ לא יהיו פונקציות ל data class.
  3. אמנם קיבלתי מימוש סטנדרטי של כמה מתודות, אבל אם צריך – אני יכול לדרוס אותן.
    בקוטלין override היא לא optional annotation, אלא mandatory keyword. טוב מאוד!
  4. הנה אני עושה השוואה בין אובייקטים של ה data class שלי. הם לא שווים, כי המימוש שמסופק ל equals – משווה את כל התכונות של המחלקה.
  5. לאחר שעשיתי השמה (שימוש ב ()clone) – האובייקטים שווים. זו אכן ההתנהגות הצפויה מ data class.

שני Data Classes שימושיים שמסופקים כחלק מהשפה הם Pair ו Triple, המאפשרים לנו להעביר זוגות או שלשות של פרמטרים.

אלו באמת רק Data Classes. למשל, הנה המימוש של Triple:

Enum Class

בדומה לג'אווה, לקוטלין יש enum… class. בואו נראה כיצד הוא נראה:

  1. הנה דוגמה ל enum פשוט. כל איבר הוא בעצם אובייקט – מופע של המחלקה.
    1. זהו אחד המקרים המעטים בהם צריך בקוטלין לכתוב יותר קוד מאשר בג'אווה: "enum class" במקום "enum"  😏
    2. מכיוון שהאיברים הם אובייקטים, אני יכול להרחיב אותם, למשל: לדרוס פונקציה של המחלקה (או להוסיף פונקציה חדשה). הפונקציות הללו שייכות לאובייקט, ולא למחלקה!
  2. אני יכול הוסיף גם פונקציה למחלקה, שתהיה זמינה לכל אחד מהאובייקטים ב enum.
  3. יש מקרה דיי נדיר בו עלי להשתמש בנקודה-פסיק בקוטלין:
    1. אם הוספתי ל enum class פונקציות, הקומפיילר יבקש עזרה לדעת היכן נגמרה רשימת האיברים, והיכן מתחילה המחלקה. ההפרדה מסומנת בעזרת נקודה-פסיק.
    2. שימו לב שב TriColor לא היינו צריכים להוסיף נקודה-פסיק (אבל יכולנו – זה תקין).
  4. מכיוון ש BLUE הוא אובייקט – כאן תופעל, באופן טבעי, הפונקציה ()toString.
  5. לכל enum class יש את הפונקציה ()valueOf, המחפשת איבר ע"פ השם שלו.
    1. ניתן להציץ בקוד המקור של ה Abstract Enum Class כאן.
  6. לכל אובייקט ב enum יש 2 תכונות מובנות:
    1. name – (ששווה לשם האיבר), בחוסר תלות מ ()toString.
    2. ordinal – שהוא האינדקס של האיבר.
  7. תכונה אחרונה חשובה של enum היא הפונקציה ()values – המחזירה מערך של האיברים. לצורך הפלט – המרתי את המערך לרשימה.
  8. אני יכול לשנות את המחלקה ולהוסיף לה תכונות דרך הבנאי הראשי.
    הנה ל TwoColor הוספתי תכונה בשם value – שיש לכל אחד מהאיברים שלה.
הנה הפלט:

סיכום

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

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

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

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

כרגיל, ננסה ללמוד עוד קצת על הדרך:

  1. הנה כמה קל להשתמש בקוד ג'אווה: אני מבצע import מתוך java.lang.Math לפונקציית החזקה (power).
    בג'אווה, בכדי לייבא מתודה הייתי צריך להדגיש: import static. בקוטלין – ויתרו על התחביר המיותר.
    שווה לציין שכל שלכל קובץ של קוטלין מתבצע מאחורי הקלעים import לשורה של חבילות: *.java.lang וכמה חבילות של קוטלין.
  2. כאשר מספר הוא עשרוני, הקומפיילר אוטומטית מניח שהוא מסוג Double. לא צריך לכתוב d בסוף המספר (אבל כן צריך לכתוב F אם אתם רוצים שיוגדר Float).
  3. כך נראית פונקציה – שוב: מאוד דומה ל Go, מאוד "מודרני".
    שימו לב לטבעיות בשימוש בפונקציה pow שהגיעה מספריה בג'אווה.
  4. הדרך להפעיל (invoke) פונקציה – היא כמו בג'אווה.
  5. זו הדרך הפשוטה להציג רק 2 ספרות עשרוניות למספר – בעזרת פונקציית format של ג'אווה.
  6. אני יכול לקרוא לפונקציה תוך כדי שאני מציין את שמות הפרמטרים כמו שהוגדרו בפונקציה (ואז סדר הארגומנטים לא משנה).
    זה אולי קריא יותר, אבל ה IntelliJ מוסיף תגיות מאוד דומות בכל מקרה (כיביתי אותן כרגע).
  7. זו הדרך לבצע formatting לאלפים + 0 ספרות לאחר הנקודה – שוב, בעזרת פונקציית format של ג'אווה.
הנה הפלט:

Nullable Types

בוודאי אתם מכירים שה Exception הנפוץ ביותר בג'אווה הוא NullPointerExcpetion.

כמו שפות מודרניות אחרות, יוצרי קוטלין החליטו להגדיר את השפה כ "null-safe by default". כעיקרון, אין nulls, אלא אם אתם מתירים אותם.

  1. זו תהיה שגיאת קומפליציה. מה פתאום לקבוע ערך null? אין מצב!
  2. אם אנחנו מגדירים ש y הוא מסוג Nullable String – אז אפשר לקבוע בו ערך null. בסדר.
  3. גם זו שגיאת קומפילציה: השתגעתם? לקרוא למתודה ()length למשהו שעשוי להיות null? אנחנו לא רוצים nullpointerException!
  4. פה הקומפיילר חכם: אם כבר בדקתי שהערך איננו null – אז יאפשרו לי לקרוא למתודות על האובייקט ה nullable.
  5. דרך אחר לעשות זאת, להשתמש באופרטור ?. במקום . – שמגן עלי בפני nullPointerException.
    אם y הוא null – אזי תוצאות כל הביטוי תהיה גם היא null.
  6. מה קורה עם מחלקה שאני מביא מג'אווה? בג'אווה – כל האובייקטים הם Nullable.
    אתם רואים שאני צריך לבנות מבנה דיי מורכב (ולא מעניין) על מנת להגן על עצמי מ NullPointerException.
    החלטה תכנונית של שפת קוטלין היא לא להגן עלי בכוח בפני אובייקטים של ג'אווה – כי כך אני רגיל. ההגנה ברמת הקומפילציה היא רק בפני אובייקטים של קוטלין שהם Nullable.
  7. בכל זאת, קוטלין מספקת לי תחביר מקוצר ובטוח לעבודה עם אובייקטים שהם Nullable. התחביר הזה תופס גם לאובייקטים של ג'אווה, וגם לאובייקטים של קוטלין שהם Nullable (ואולי יש להם פונקציה שערך ההחזרה שלה הוא Nullable).
    במקרה הזה, מכיוון ש ()getExtID מחזיר null (נניח), כל הביטוי יוערך כ null – ולא ייזרק NullPointerException.
  8. לקוטלין יש כמה כלים לעבוד עם רשימות שחלק מהאיברים בהן עשוי להיות null.
    גישה ראשונה היא השימוש בפונקציה let שתפעל רק אם הערך הוא לא null.
    אפשר להשתמש ב let גם ללא קשר לרשימת איברים.
  9. גישה שניה, היא להשתמש בפונקציה / פילטר בשם ()filterNotNull, כלומר: להשאיר רשימה הכוללת רק איברים שאינם null.
  10. אני יכול לבדוק אם משתנה הוא null ולהגיב בהתאם.
  11. או בגלל שזו פעולה שכיחה, להשתמש באופרטור ה 😕 (נקרא Elvis Operator) – בכדי להשיג בדיוק אותו הדבר בכתיבה מקוצרת.
  12. אם אני ממש מתעקש, אני לומר לקומפיילר: "עזוב אותי באמאשלך! אני אסתדר עם ה null-ים שלי בעצמי", בעזרת אופרטור ה .!!.
    1. שימו לב שאת האופרטור הזה אפשר להפעיל רק על משתנים שהם immutable (למשל: val) – אחרת, יכול תאורטית thread אחר להיכנס בין "שורות" ההפעלה שלי – ולשים בו null.
      1. אופס!, חטפתי KotlinNullPointerException.

הנה הפלט:

ערכי החזרה מיוחדים

שימו לב לחבר'ה הבאים:

1. לפונקציה foo לא הגדרנו ערך החזרה. היא בעצם בעלת ערך החזרה מטיפוס Unit (בצורה לא מפורשת).
    Unit הוא כמו void בג'אווה – אבל ניתן לשמור את הערך (עבור גנריות של הקוד).
אם יופיע בלוג "kotlin.Unit" – מכאן זה מגיע.

2. ניתן להגדיר את אותה הפונקציה כ Unit בצורה מפורשת.
3. הפונקציה fail היא מסוג Unit, אבל בעצם היא לעולם לא תחזיר Unit – כי תמיד היא זורקת Exception (שימו לב שבקוטלין לא משתמשים ב new).
4. ניתן להגדיר פונקציה שלעולם לא תגיע ל return – כבעלת ערך החזרה מטיפוס Nothing.

את Nothing לא ניתן להציב במשתנה – זו בעצם הערה לקומפיילר.

5. כאן נקבל שגיאת קומפליציה מסוג Conflicting declaration – הקומפיילר לא יודע איזה טיפוס להגדיר ל data:
Int – בגלל Zoo?
או אולי Any (ה root בהיררכיית האובייקטים בקוטלין, כולל null) – בגלל ש fail הוא מטיפוס Unit?

6. במקרה הזה הקופיילר רגוע: מ ()gail לא יכול לחזור ערך (הוא מסוג Nothing), ולכן data יוגדר כטיפוס Int.

מעניין אולי לציין ש null הוא singleton מטיפוס ?Nothing.

default value ו vararg

  1. אני יכול להגדיר בפונקציה פרמטרים עם ערך ברירת מחדל – למשל: cores.
  2. אני יכול לקרוא לפונקציה עם כל הארגומנטים (ה annotations הם של ה IDE).
  3. אני יכול לקרוא לפונקציה ולהשמיט את הארגומנט – ואז לקבל את ערך ברירת המחדל.
  4. הנה הגדרתי פונקציה לחישוב ממוצע ה CPU utilization במכונה – ע"י חישוב הממוצע של ה cores.
    שימו לב שניתן להשתמש בכתיב מקוצר (=), אם הפונקציה גוף הפונקציה הוא ביטוי (expression) בודד – גם אם אין ערך החזרה. הגדרה כזו נקראת shorthand function.
  5. אני יכול גם להגדיר מספר משתנה של פרמטרים – בעזרת המילה vararg (המקבילה של … בג'אווה).
    המשתנה core הוא מטיפוס <Array<out T  – אסביר את הנושא של out, כשנגיע ל Generics.אם יקראו ל printStats עם 2 פרמטרים – הפונקציה הראשונה תופעל.
    אם ל printStats עם מספר שונה של פרמטרים – הפונקציה השנייה תופעל.

התוצאה:

פונקציות מסדר גבוה

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

  1. הנה אנחנו מגדירים פונקציה בשם profile המקבלת פונקציה אחרת (func) כפרמטר.
    התחביר הוא סוגריים עם רשימת משתנים, חץ (<-) וערך החזרה.
    מעבר לכך, זהו פרמטר לכל דבר.
  2. בכדי להוסיף ערך מוסף כלשהו, השתמשתי בפונקציית ה profiling של ג'אווה, המחזירה את הזמן הנוכחי בננו-שניות.
  3. הפעלה של הפונקציה func נעשית בצורה טבעית למדי.
  4. עכשיו אגדיר 2 פונקציות דומות המתאימות בחתימה לפרמטר func של profile: מקבלות 2 מספרים שלמים – ומחזירות ערך של מספר שלם
  5. כעת אני סקרן לדעת איזו פונקציה יעילה יותר מבחינת ביצועים!
    כך אני מפעיל את הפונקציה "הגבוהה" profile:
    הקומפיילר צריך לדעת שאני רוצה לשלוח רפרנס לפונקציה ולא להפעיל אותה, את זה עושים בעזרת האופרטור :: – המחזיר reference לפנוקציה.
    הקפדתי על ארגומטים עם ערך זהה, על מנת לא להטות את בדיקת הביצועים 😉
    אם אני רוצה רפרנס למתודה של אובייקט, התחביר הוא object::method.
  6. מה קורה כאשר אני רוצה להגדיר חתימה של פונקציה שלא מחזירה כלום?
    אם אגדיר רק (Int) – הקומפיילר יתעלם מהסוגריים ויניח שמדובר בפרמטר מסוג Int.
    עלי לציין ערך החזרה מסוג Unit (כפי שראינו מוקדם יותר בפוסט) – ואז הקוד יתנהג כמצופה.
הנה הפלט:

אלף ננו-שניות הן מיקרו-שניה (ולא מילי-שניה, לא לבלבל!).
הממ… משהו מוזר לי בזמנים של הפונקציה add – הייתי מצפה לכ 50,000 ננו-שניות (5 הפעלות של פונקציה) ומטה (אופטימיזציות של הקומפיילר)…

Lambda Expressions

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

לא היה נחמד יותר לכתוב את גוף הפונקציה inline?

  1. הנה אני יכול לכתוב את הפונקציה inline, כ anonymous function. זה התחביר – ללא שם. זה עדיין לא מאוד אלגנטי.
  2. יותר אלגנטי יהיה לכתוב את אותה הפונקציה inline כ lambda expression. זה התחביר.
  3. מכיוון שהקומפיילר יודע מה החתימה של profile – זה מיותר לציין לו טיפוסים על הפרמטרים, ולכן ניתן לכתוב את הפונקציה בצורה קצרה יותר.
  4. חשוב לציין שאם אני עושה extract variable ל lambda expression, והיא כבר לא inline – הקומפיילר לא יידע להסיק את הטיפוסים של הפרמטרים – ויש לציין אותם בחזרה.
  5. לצורך ההסבר, נעבור לעבוד עכשיו עם פונקציה דומה ל profile בשם profileOne, המצפה לפרמטר אחד בלבד.
    שימו לב שהחלפתי את סדר הפרמטרים בפונקציה, כאשר func הוא הפרמטר האחרון. מייד אסביר מדוע זאת היא הקונבנציה ב higher order functions בקוטלין.
  6. כאשר יש פרמטר יחיד לפונקציה, לא צריך להגדיר אותו. אפשר להשתמש בצורה מקוצרת המדלגת על ההגדרה (<- num) ישר לגוף הפונקציה. אם לא הוגדר שם משתנה – ההתייחסות אליו תהיה כ it.
    זה יכול להיות קיצור נחמד, אבל הייתי ממליץ לכם להימנע ממנו כאשר יש פונקציות מקוננות – אז זה נהיה מבלבל.
  7. הנה הסיבה מדוע הקונבנציה בקוטלין היא שהפונקציה תהיה הפרמטר האחרון: אם ורק אם הפונקציה היא הפרמטר האחרון, אפשר לכתוב את גוף הפונקציה מחוץ לסוגריים.
  8. למה זה טוב? זה שימושי אם אתם רוצים לייצר משהו שדומה ל DSL (יש עוד מה להרחיב בנושא בפוסטי המשך).
    למשל: הגדרתי את הפונקציה enhance (שאולי מוסיפה לוגים, בדיקות אבטחה, ניטור, וכיוצ"ב) ואז אני פשוט שולח לה קוד. זה דומה מאוד ל DSL.
  9. אשנה קצת את ה layout של הקוד בכדי להדגיש: אתם יכולים להגדיר פונקציות שלכם, שנראות "כמו" מילה בשפה. כמה מבלבל הוא לראות enhance שכזה (מבלי להכיר את קוטלין) – ולא למצוא בגוגל מה הוא עושה 😏
אז עם כל התחביר הנחמד הזה של למבדה, מה הטעם להשאיר את ה Anonymous Function?
יש מקרים שבהם הוא בכל זאת שימושי, למשל: כאשר אתם רוצים לעשות return מתוך מקומות שונים בתוך הפונקציה.
מקרה אחר: אני יכול להגדיר return type – שאולי הוא subtype של מה שמגדירה הפונקציה מסדר-גבוה, המארחת.
Closures
פונקציה אנונימית ו lambda expression מסוגלים לגשת למשתנים של הפונקציה שעוטפת אותם – וגם לשנות אותם (זה שוני מג'אווה).

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

משתנה שניגשנו אליו בתוך Closure לא "יוקפא הערך שלו" – כמו בג'אווהסקיפט, למשל.

לאחר הסרת השורה הבעייתית, הנה הפלט:

סיכום

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

האם סיימנו לכסות את נושא הפונקציות?
עוד לא.

יש עוד Local Function, Infix Functions, Inline Functions, Extension Functions – ואולי עוד שכחתי משהו.

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

הנה פוסט ההמשך.

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

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

קוטלין?!

קוטלין (על שם האי קוטלין) היא שפה static-typed, עם תחביר מודרני, הרצה מעל ה JVM. היא פותחה ע"י JetBrains – חברת ה IDEs הנודעת מרוסיה.

החבר'ה של JetBrains כתבו הרבה קוד בג'אווה כחלק מפיתוח ה IDEs שלהם. התחושה – שהקוד verbose מדי. הם התלבטו בין Ceylon (של RedHat, המתקמפלת גם לג'אווהסקריפט) וסקאלה, אבל ממספר סיבות – החליטו שאף אחת מהשתיים אינה מתאימה להם.

הפתרון: לפתח שפה בעצמם.

שש שנים אחרי, חברות טכנולוגיות כמו Uber, Netflix, Pinterest ועוד – משתמשות בקוטלין: בצד השרת, ובאנדרואיד.
קוטלין היא דיי חדשה, ה GA שלה הוא החל מפברואר 2016.

בכל זאת, גוגל הכריזה לאחרונה על קוטלין כשפת First Class באדרואיד – נקודת מפנה של ממש באימוץ השפה.

למרות סיכויים שנראו (לי אישית) קטנים בהתחלה – קוטלין הולכת ותופסת מקום משמעותי מאוד כשפה אלטרנטיבית וחשובה על ה JVM.

בקצרה:

  • לקוטלין יש תחביר דומה מאוד לג'אווה – אבל מודרני ומינימליסטי. אלמנטים רבים בתחביר שלה מזכירים את השפה האחרונה שסקרתי בבלוג – Go.
  • פונקציות הן First Class Citizens. שמעתי טענה ש Kotlin היא "שפת Object-Oriented ו Functional" – טענה מוגזמת לטעמי: immutability ו pure functions הן לא משהו מפותח בשפה.
  • לקוטלין יש Interoperability טוב מאוד עם ג'אווה – הרבה יותר טוב מסקאלה.
  • לקוטלין יש coroutines (יכולת שהיא עדיין ניסיונית) – היכולת להריץ תהליכים רזים ברמת ה runtime של השפה, עם כלים פשוטים לטיפול במקביליות, כמו async, yield ו await.
  • לקוטלין יש IDE מצוין – עם תמיכה מתקדמת. זה חשוב.
  • קוטלין יכולה להתקמפל לג'אווהסקריפט. שימו לב שלא כל תוכן הספריות הסטנדרטיות תומכות גם ב JVM וגם ב JS – ויש סימון מתאים. ה Interoperability עם ג'אווהסקריפט הוא קצת יותר מורכב, בשל האופן הדינאמי של ג'אווהסקריפט.
  • בדומה ל Swift – הגבילו בשפה את השימוש ב Null.
  • אין Checked Exceptions (יששש!)
מצד שני:
  • קוטלין היא שפה עשירה ולא פשוטה. היא יותר מורכבת מג'אווה – אם כי יותר פשוטה מסקאלה.
  • קוטלין מתקמפלת מעט לאט יותר מג'אווה (היינו רוצים שהתהליך יהיה מהיר יותר).

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

שלום, קוטלין!

תודו שפאן (fun) נשמע הרבה יותר טוב מדף' (def – נשמע כמו מוות)

הנה ה Hello World של קוטלין.

אני חושב שלמי שכתב בג'אווה, אבל מכיר גם שפות מינימליסטיות יותר (פייטון?) – זה נראה כמו ברכה!
קוטלין היא עדיין שפת Object-Oriented עשירה לכל דבר ועניין. ישנם קיצורים לשימושים נפוצים (למשל: println)

הקוד בקוטלין מתקמפל ל bytecode של ה JVM (עם הסיומת kt לשם קובץ ה class.):

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

את ה class שנוצר בקומפליציה לא ניתן להריץ, ללא ה runtime של קוטלין: kotlin-runtime.jar.

אם עובדים עם command line הכי פשוט לארוז את הפרויקט ב jar עם הקומפיילר של קוטלין, לצרף את ה runtime – ואז להריץ:

$ kotlinc -include-runtime -d
$ java -jar
משתנים והשמות
  1. משתנה מגדירים בעזרת המילה השמורה var. קודם מופיע שם המשתנה – ורק אז הטיפוס (הפוך מג'אווה או C).
  2. ניתן באותה השורה גם לאתחל ערך.
  3. בעצם, ונראה את זה שוב ושוב: מה שהקומפיילר יכול להסיק לבד – לא צריך להגדיר לו. c יוכר כ Int כי זה הערך שהשמנו לו.
    var אינו "כלי קיבול גנרי". c מעתה והלאה – הוא static type מסוג Int.
  4. המילה השמורה val מגדירה ערכים שהם "immutable" (ליתר דיוק: פשוט לא ניתן לקבוע ערך אחר – בדומה ל final בג'אווה).
    בדומה לרובי, ניתן להשתמש במספר בקו תחתון, בכדי להקל על קריאת אלפים (או כל חלוקה אחרת שתרצו).
  5. בדומה לג'אווה, אני יכול להכריז שערך הוא מטיפוס Long ע"י הוספת האות "L" בסוף המספר.
  6. זו שגיאת קומפילציה. מדוע?
    כי המשתנה a לא אותחל, וקוטלין לא מקבלת השמה לערך שאינו מוגדר. אין אתחול באפס או null.
    בקוטלין כל המספרים הם אובייקטים. אין פרמיטיביים.
    אובייקטים לא יכולים לקבל ערך null, אלא אם הגדרתי אותם ככאלו (על כך בפוסט המשך).
  7. גם זו שגיאת קומפילציה! מדוע?
    אם אני מציב Long ב Integer (או להיפך) – עלי להצהיר על הטרנספורציה במפורש.
    אין casting, ואופן הצרנספורמציה הוא שימוש בפונקציה שהתנהגותה מוגדר היטב.
  8. כך צריך לבצע את ההמרה. להזכיר: b הוא אובייקט לכל דבר – אין פה autoboxing.

מחרוזות

  1. מחרוזת היא עוד אובייקט בקוטלין, כאשר יש ערך – לא צריך להכריז על טיפוס.
  2. כמו בפייטון (או עוד שפות) ניתן להגדיר Raw String בעזרת מרכאות משולשות.
    1. Raw Strings לא מקבלות escaping: הביטוי n\\ הוא מחרוזת, ולא שורה חדשה.
    2. כמו בשפות אחרות, ה margin הוא חלק מהמחרוזת.
    3. בכדי לצמצם margin ניתן לסמן את ה margin הרצוי בעזרת סימן "|" בתחילת כל שורה, ולקרוא ל ()trimMargin.
  3. בקוטלין יש interpolation למחרוזות בעזרת סימן ה $. כאשר הביטוי שם אטומי (יחיד) של משתנה – מספיק רק סימן הדולר.
  4. כאשר הביטוי הוא יותר מאטום אחד – יש לעטוף אותו בסוגריים מסולסלים.
  5. אם רוצים פשוט להקליד $ – יש לעשות לו escaping.
  6. בקוטלין יש custom operators, והשוואה בין מחרוזות מתרחש באמצעות האופרטור == (המקביל ל ()equals)

למען הסר ספק, הנה הפלט של הקוד לעיל:

לולאות

  1. כמו בפייטון, ניתן להשתמש ב range.
    rangeTo (כלומר: ..) הוא בעצם אופרטור של אובייקט ה Int, המחזיר אובייקט מסוג IntRange שהוא <Iterable<Int.
  2. ניתן, כמובן, לעשות איטרציה גם על <Iterable<T. דאא.
    מעניין לציין ש Iterable הוא לא Lazy, והופך לרשימה מלאה בעת ה evaluation שלו. השיקול: ביצועים. זיכרון רציף יעיל יותר לגישה ע"י המעבד.
  3. אפשר גם לספור לאחור, בעזרת downTo ויש גם step.
    downTo ו step הן לא מלים שמורות בשפה, אלא infix functions – כלי דומה לאופרטור, ששווה להסביר עליו בפוסט המשך.
  4. ()listOf היא פונקציה גנרית שמקבל רשימה (varargs) של ארגומנטים – ומחזיר אותם כרשימה <List<T. כמובן ש List, הוא גם Iterable.
  5. אם אני רוצה לבצע איטרציה ולעבוד עם אינדקס – זה התחביר.
  6. אם אני רוצה להתעלם ממשתנה (להימנע ב wanrnings / קוד קריא יותר), כמו בשפת Go – אני מחליף את המשתנה שלא אשתמש בו ב _ .
    (כמובן שבמקרה הזה שלב 4 הוא הדרך הפשוטה לכתוב את הקוד).

הנה הפלט:

נמשיך עוד קצת:

בזריזות:

  1. יש while.
  2. יש do {} while.
  3. בקוטלין אפשר להגדיר Label (מסתיים ב @) אליו אפשר להתייחס בלולאות מקוננות.
    כמה פשוט ונוח לומר: "אני רוצה להפעיל continue, אבל לא ללולאה הפנימית – אלא ללולאה החיצונית".
    הפקודה הן <break@<Label או <continue@<Label.
  4. יש גם repeat. האנוטציה ":times" היא של ה IDE – ואינה חלק מהקוד.
    זה מה שנקרא: "Five Whys".
הנה הפלט:
קצת על Control Flow
 
  1. בקוטלין אין ternary operator, קרי: v = cond ? a: b.
    הדרך המקבילה הכי פשוטה היא one liner if..else.
  2. משהו נחמד הוא ש if..else נחשב כ expression.
    1. אם, ורק אם יש לנו גם if וגם else (הרי אנחנו רוצים להימנע מ null / ערך לא מוגדר) – ניתן "לאסוף" את הערך מתוך ה expression.
    2. הערך הוא תוצאת ה evaluation של השורה האחרונה בכל בלוק – ממש כמו בשפת רובי.
    3. זה בעצם מה שמתרחש בסעיף #1 – רק שסעיף #1 הוא הצורה הפשוטה יותר לתבנית הזו.
  3. במקום switch, יש בקטולין את when.
    1. כפי שאתם רואים אפשר להשתמש במגוון ביטויים.
    2. בניגוד ל switch, ה evaluation של התנאים יפסיקו ברגע שימצא התנאי הראשון שהוא אמת. לכן הפלט יהיה ש "5 הוא ספרה בודדת", אף על פי ש 5 הוא גם מספר ראשוני, ואינו בסדרת ה 20.
הנה הפלט:

סיכום

זה נראה לי מספיק בתור התחלה.
אמנם מה שעברנו עליו הם בעיקר דברים טריוויאליים – אבל חשוב להכיר אותם.
בכדי לכסות את קוטלין בצורה טובה, נראה לי שאזדקק לעוד… 5 פוסטים בערך (יצאו 8, הנה הסדרה המלאה):

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

—-

לינקים רלוונטיים

Cheat Sheet למעבר בין ג'אווה לקוטלין. https://github.com/MindorksOpenSource/from-java-to-kotlin