Kotlin Bootcamp for Programmers 5.2: Generics

ה-codelab הזה הוא חלק מקורס Kotlin Bootcamp for Programmers. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. אם יש לכם ידע בנושא, יכול להיות שתוכלו לדלג על חלק מהקטעים. הקורס הזה מיועד למתכנתים שמכירים שפה מונחית-אובייקטים ורוצים ללמוד Kotlin.

מבוא

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

במקום ליצור אפליקציה לדוגמה אחת, השיעורים בקורס הזה נועדו להרחיב את הידע שלכם, אבל הם גם עצמאיים למחצה, כך שתוכלו לדלג על חלקים שאתם מכירים. כדי לקשור בין הדוגמאות, רבות מהן מתבססות על נושא האקווריום. אם אתם רוצים לראות את הסיפור המלא של האקווריום, כדאי לעיין בקורס Kotlin Bootcamp for Programmers ב-Udacity.

מה שכדאי לדעת

  • התחביר של פונקציות, מחלקות ושיטות ב-Kotlin
  • איך יוצרים כיתה חדשה ב-IntelliJ IDEA ומריצים תוכנית

מה תלמדו

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

הפעולות שתבצעו:

  • יצירת קטגוריה גנרית והוספת הגבלות
  • יצירת סוגים של in ושל out
  • יצירת פונקציות גנריות, שיטות ופונקציות הרחבה

מבוא ל-generics

ב-Kotlin, כמו בשפות תכנות רבות, יש סוגים גנריים. סוג גנרי מאפשר להפוך מחלקה לגנרית, וכך להפוך אותה לגמישה הרבה יותר.

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

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

class MyList<T> {
    fun get(pos: Int): T {
        TODO("implement")
    }
    fun addItem(item: T) {}
}

אפשר להפנות אל T כאילו היה סוג רגיל. סוג ההחזרה של get() הוא T, והפרמטר של addItem() הוא מסוג T. כמובן שרשימות גנריות הן שימושיות מאוד, ולכן המחלקה List מובנית ב-Kotlin.

שלב 1: יוצרים היררכיית סוגים

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

  1. כדי שהדוגמה תהיה ברורה, יוצרים חבילה חדשה בתיקייה src וקוראים לה generics.
  2. בחבילה generics, יוצרים קובץ Aquarium.kt חדש. כך תוכלו להגדיר מחדש דברים באמצעות אותם שמות בלי שיהיו התנגשויות, ולכן שאר הקוד שלכם בסדנת הקוד הזו ייכנס לקובץ הזה.
  3. יצירת היררכיה של סוגי אספקת מים. כדי שיהיה אפשר ליצור מחלקת משנה, צריך להתחיל עם יצירת מחלקה WaterSupply מסוג open.
  4. מוסיפים פרמטר בוליאני var, ‏ needsProcessing. הפעולה הזו יוצרת אוטומטית נכס שניתן לשינוי, יחד עם getter ו-setter.
  5. יוצרים מחלקת משנה TapWater שמרחיבה את WaterSupply, ומעבירים את true ל-needsProcessing, כי מי הברז מכילים תוספים שמזיקים לדגים.
  6. ב-TapWater, מגדירים פונקציה בשם addChemicalCleaners() שקובעת את הערך של needsProcessing ל-false אחרי ניקוי המים. אפשר להגדיר את המאפיין needsProcessing מ-TapWater, כי הוא public כברירת מחדל ונגיש למחלקות משנה. הנה הקוד המלא.
package generics

open class WaterSupply(var needsProcessing: Boolean)

class TapWater : WaterSupply(true) {
   fun addChemicalCleaners() {
       needsProcessing = false
   }
}
  1. יוצרים עוד שני תת-מחלקות של WaterSupply, בשם FishStoreWater ו-LakeWater. לא צריך לעבד את FishStoreWater, אבל צריך לסנן את LakeWater באמצעות השיטה filter(). אחרי הסינון, אין צורך לעבד אותו שוב, ולכן בהגדרה filter(), מגדירים את needsProcessing = false.
class FishStoreWater : WaterSupply(false)

class LakeWater : WaterSupply(true) {
   fun filter() {
       needsProcessing = false
   }
}

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

שלב 2: יצירת קטגוריה גנרית

בשלב הזה משנים את המחלקה Aquarium כדי לתמוך בסוגים שונים של אספקת מים.

  1. ב-Aquarium.kt, מגדירים מחלקה Aquarium, עם <T> בסוגריים אחרי שם המחלקה.
  2. הוספת מאפיין קבוע waterSupply מסוג T אל Aquarium.
class Aquarium<T>(val waterSupply: T)
  1. כותבים פונקציה בשם genericsExample(). התג הזה לא שייך לכיתה, ולכן אפשר להוסיף אותו לרמה העליונה של הקובץ, כמו הפונקציה main() או הגדרות הכיתה. בפונקציה, יוצרים Aquarium ומעבירים לו WaterSupply. הפרמטר waterSupply הוא פרמטר כללי, ולכן צריך לציין את הסוג שלו בתוך סוגריים זוויתיים <>.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
}
  1. ב-genericsExample() הקוד שלכם יכול לגשת ל-waterSupply של האקווריום. מכיוון שהסוג שלו הוא TapWater, אפשר להתקשר אל addChemicalCleaners() בלי לבצע המרה של סוגים.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. כשיוצרים את האובייקט Aquarium, אפשר להסיר את הסוגריים הזוויתיים ואת מה שביניהם כי ל-Kotlin יש היסק סוגים. לכן, אין סיבה להשתמש ב-TapWater פעמיים כשיוצרים את המופע. אפשר להסיק את הסוג מהארגומנט של Aquarium, אבל עדיין תתבצע Aquarium מסוג TapWater.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. כדי לראות מה קורה, מדפיסים את needsProcessing לפני ואחרי הקריאה ל-addChemicalCleaners(). בהמשך מופיעה הפונקציה המלאה.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
    aquarium.waterSupply.addChemicalCleaners()
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
}
  1. מוסיפים פונקציה main() כדי לקרוא ל-genericsExample(), ואז מריצים את התוכנית ומתבוננים בתוצאה.
fun main() {
    genericsExample()
}
⇒ water needs processing: true
water needs processing: false

שלב 3: הופכים את ההנחיה לספציפית יותר

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

  1. ב-genericsExample(), יוצרים Aquarium, מעבירים מחרוזת ל-waterSupply, ואז מדפיסים את המאפיין waterSupply של האקווריום.
fun genericsExample() {
    val aquarium2 = Aquarium("string")
    println(aquarium2.waterSupply)
}
  1. מריצים את התוכנית ומסתכלים על התוצאה.
⇒ string

התוצאה היא המחרוזת שהועברה, כי Aquarium לא מגביל את T.אפשר להעביר כל סוג, כולל String.

  1. ב-genericsExample(), יוצרים עוד Aquarium ומעבירים את null ל-waterSupply. אם הערך של waterSupply הוא null, הפונקציה מחזירה "waterSupply is null".
fun genericsExample() {
    val aquarium3 = Aquarium(null)
    if (aquarium3.waterSupply == null) {
        println("waterSupply is null")
    }
}
  1. מריצים את התוכנית ומעיינים בתוצאה.
⇒ waterSupply is null

למה אפשר להעביר את הערך null כשיוצרים Aquarium? האפשרות הזו קיימת כי כברירת מחדל, T מייצג את הסוג Any? שניתן להגדיר לו ערך null, הסוג שנמצא בראש היררכיית הסוגים. התוכן הבא זהה למה שהקלדת קודם.

class Aquarium<T: Any?>(val waterSupply: T)
  1. כדי לא לאפשר העברה של null, צריך להגדיר את T כסוג Any באופן מפורש, על ידי הסרת ? אחרי Any.
class Aquarium<T: Any>(val waterSupply: T)

בהקשר הזה, Any נקרא מגבלה כללית. כלומר אפשר להעביר כל טיפוס ל-T כל עוד הוא לא null.

  1. מה שאתם באמת רוצים זה לוודא שאפשר להעביר רק WaterSupply (או אחת ממחלקות המשנה שלו) ל-T. כדי להגדיר אילוץ גנרי ספציפי יותר, מחליפים את Any ב-WaterSupply.
class Aquarium<T: WaterSupply>(val waterSupply: T)

שלב 4: הוספת בדיקות נוספות

בשלב הזה נסביר על הפונקציה check() כדי לוודא שהקוד מתנהג כמו שציפיתם. הפונקציה check() היא פונקציה רגילה בספרייה ב-Kotlin. הפונקציה פועלת כהצהרה ותחזיר את השגיאה IllegalStateException אם הארגומנט שלה יחזיר את הערך false.

  1. מוסיפים שיטה addWater() למחלקה Aquarium כדי להוסיף מים, עם check() שמוודא שלא צריך לעבד את המים קודם.
class Aquarium<T: WaterSupply>(val waterSupply: T) {
    fun addWater() {
        check(!waterSupply.needsProcessing) { "water supply needs processing first" }
        println("adding water from $waterSupply")
    }    
}

במקרה כזה, אם needsProcessing הוא true, הפונקציה check() תזרוק חריגה.

  1. ב-genericsExample(), מוסיפים קוד כדי ליצור Aquarium עם LakeWater, ואז מוסיפים קצת מים.
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.addWater()
}
  1. מריצים את התוכנית ומקבלים חריגה, כי צריך לסנן את המים קודם.
⇒ Exception in thread "main" java.lang.IllegalStateException: water supply needs processing first
        at Aquarium.generics.Aquarium.addWater(Aquarium.kt:21)
  1. מוסיפים שיחה כדי לסנן את המים לפני שמוסיפים אותם ל-Aquarium. עכשיו, כשמריצים את התוכנית, לא מתקבלת חריגה.
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.waterSupply.filter()
    aquarium4.addWater()
}
⇒ adding water from generics.LakeWater@880ec60

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

במשימה הזו תלמדו על סוגי in ו-out עם גנריקה. סוג in הוא סוג שאפשר להעביר רק למחלקה, ולא להחזיר. סוג out הוא סוג שאפשר להחזיר רק ממחלקה.

אם תסתכלו על המחלקה Aquarium, תראו שהסוג הגנרי מוחזר רק כשמקבלים את המאפיין waterSupply. אין שיטות שמקבלות ערך מסוג T כפרמטר (חוץ מהגדרת הערך בבונה). ב-Kotlin אפשר להגדיר out סוגים בדיוק למקרה הזה, והמערכת יכולה להסיק מידע נוסף לגבי המקומות שבהם אפשר להשתמש בסוגים בצורה בטוחה. באופן דומה, אפשר להגדיר סוגים של in לסוגים גנריים שמועברים רק לשיטות, ולא מוחזרים. כך Kotlin יכולה לבצע בדיקות נוספות כדי לוודא שהקוד בטוח.

הסוגים in ו-out הם הנחיות למערכת הסוגים של Kotlin. הסבר על כל מערכת הסוגים חורג מהיקף ההדרכה הזו (מדובר במערכת מורכבת למדי), אבל הקומפיילר יסמן סוגים שלא סומנו כראוי ב-in וב-out, ולכן חשוב להכיר אותם.

שלב 1: הגדרת סוג של הוצאה

  1. בכיתה Aquarium, משנים את T: WaterSupply לסוג out.
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    ...
}
  1. באותו קובץ, מחוץ למחלקה, מכריזים על פונקציה addItemTo() שמצפה ל-Aquarium של WaterSupply.
fun addItemTo(aquarium: Aquarium<WaterSupply>) = println("item added")
  1. מתקשרים אל addItemTo() מ-genericsExample() ומריצים את התוכנית.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    addItemTo(aquarium)
}
⇒ item added

‫Kotlin יכולה לוודא ש-addItemTo() לא תבצע פעולות לא בטוחות מבחינת סוג עם הגנרי WaterSupply, כי היא מוצהרת כסוג out.

  1. אם מסירים את מילת המפתח out, הקומפיילר יציג שגיאה כשקוראים ל-addItemTo(), כי Kotlin לא יכולה להבטיח שלא מתבצעות פעולות לא בטוחות עם הסוג.

שלב 2: הגדרת סוג ה-IN

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

  1. ב-Aquarium.kt, מגדירים ממשק Cleaner שמקבל פרמטר כללי T שמוגבל ל-WaterSupply. מכיוון שהיא משמשת רק כארגומנט ל-clean(), אפשר להגדיר אותה כפרמטר in.
interface Cleaner<in T: WaterSupply> {
    fun clean(waterSupply: T)
}
  1. כדי להשתמש בממשק Cleaner, יוצרים מחלקה TapWaterCleaner שמטמיעה את Cleaner לניקוי TapWater על ידי הוספת כימיקלים.
class TapWaterCleaner : Cleaner<TapWater> {
    override fun clean(waterSupply: TapWater) =   waterSupply.addChemicalCleaners()
}
  1. בכיתה Aquarium, מעדכנים את addWater() כדי לקחת Cleaner מסוג T, ומנקים את המים לפני שמוסיפים אותם.
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    fun addWater(cleaner: Cleaner<T>) {
        if (waterSupply.needsProcessing) {
            cleaner.clean(waterSupply)
        }
        println("water added")
    }
}
  1. מעדכנים את קוד הדוגמה genericsExample() כדי ליצור TapWaterCleaner, ‏ Aquarium עם TapWater, ואז מוסיפים קצת מים באמצעות חומר הניקוי. הוא ישתמש בכלי לניקוי לפי הצורך.
fun genericsExample() {
    val cleaner = TapWaterCleaner()
    val aquarium = Aquarium(TapWater())
    aquarium.addWater(cleaner)
}

‫Kotlin תשתמש במידע על הסוגים in ו-out כדי לוודא שהקוד משתמש בגנריות בצורה בטוחה. קל לזכור את Out ו-in: אפשר להעביר החוצה סוגים של out כערכי החזרה, ואפשר להעביר פנימה סוגים של in כארגומנטים.

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

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

שלב 1: יוצרים פונקציה גנרית

  1. ב-generics/Aquarium.kt, יוצרים פונקציה isWaterClean() שמקבלת Aquarium. צריך לציין את הסוג הגנרי של הפרמטר. אפשרות אחת היא להשתמש ב-WaterSupply.
fun isWaterClean(aquarium: Aquarium<WaterSupply>) {
   println("aquarium water is clean: ${aquarium.waterSupply.needsProcessing}")
}

אבל המשמעות היא שצריך להגדיר ל-Aquarium פרמטר מסוג out כדי שאפשר יהיה לקרוא לו. לפעמים out או in מגבילים מדי כי צריך להשתמש בסוג גם לקלט וגם לפלט. אפשר להסיר את הדרישה out על ידי הפיכת הפונקציה לגנרית.

  1. כדי להפוך את הפונקציה לגנרית, מוסיפים סוגריים זוויתיים אחרי מילת המפתח fun עם סוג גנרי T ואילוצים כלשהם, במקרה הזה WaterSupply. משנים את Aquarium כך שיוגבל על ידי T במקום על ידי WaterSupply.
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) {
   println("aquarium water is clean: ${!aquarium.waterSupply.needsProcessing}")
}

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

  1. כדי להפעיל את הפונקציה isWaterClean(), מציינים את הסוג בסוגריים זוויתיים מיד אחרי שם הפונקציה ולפני הסוגריים.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean<TapWater>(aquarium)
}
  1. בגלל היסק הסוג מהארגומנט aquarium, אין צורך בסוג, ולכן צריך להסיר אותו. מריצים את התוכנית ומסתכלים על הפלט.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean(aquarium)
}
⇒ aquarium water is clean: false

שלב 2: יוצרים שיטה גנרית עם סוג מוחשי

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

  1. בכיתה Aquarium, מכריזים על שיטה, hasWaterSupplyOfType(), שמקבלת פרמטר כללי R (כבר נעשה שימוש ב-T) שמוגבל ל-WaterSupply, ומחזירה true אם waterSupply הוא מסוג R. הפונקציה הזו דומה לפונקציה שהצהרתם עליה קודם, אבל היא נמצאת בתוך המחלקה Aquarium.
fun <R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R
  1. שימו לב שהתוצאה הסופית R מודגשת בקו תחתון אדום. מעבירים את הסמן מעל הסמל כדי לראות מה השגיאה.
  2. כדי לבצע בדיקת is, צריך לציין ב-Kotlin שהסוג הוא reified, כלומר אמיתי, ושאפשר להשתמש בו בפונקציה. כדי לעשות זאת, מוסיפים inline לפני מילת המפתח fun ו-reified לפני סוג מילת המפתח הכללית R.
inline fun <reified R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R

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

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

  1. מזינים את הערך TapWater כסוג. בדומה לקריאה לפונקציות גנריות, קוראים לשיטות גנריות באמצעות סוגריים זוויתיים עם הסוג אחרי שם הפונקציה. מריצים את התוכנית ומעיינים בתוצאה.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())   // true
}
⇒ true

שלב 3: יצירת פונקציות של תוסף

אפשר להשתמש בסוגים מוחשיים גם בפונקציות רגילות ובפונקציות הרחבה.

  1. מחוץ למחלקה Aquarium, מגדירים פונקציית הרחבה ב-WaterSupply בשם isOfType() שבודקת אם WaterSupply שעבר הוא מסוג ספציפי, למשל TapWater.
inline fun <reified T: WaterSupply> WaterSupply.isOfType() = this is T
  1. מפעילים את פונקציית התוסף בדיוק כמו שמפעילים method.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.waterSupply.isOfType<TapWater>())  
}
⇒ true

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

  1. כדי להשתמש בהטלת כוכבים, מזינים <*> אחרי Aquarium. העברה של hasWaterSupplyOfType() לפונקציית הרחבה, כי היא לא באמת חלק מליבת ה-API של Aquarium.
inline fun <reified R: WaterSupply> Aquarium<*>.hasWaterSupplyOfType() = waterSupply is R
  1. משנים את השיחה ל-hasWaterSupplyOfType() ומריצים את התוכנית.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())
}
⇒ true

בדוגמה הקודמת, הייתם צריכים לסמן את הסוג הגנרי כ-reified ולהגדיר את הפונקציה כ-inline, כי Kotlin צריכה לדעת עליהם בזמן הריצה, ולא רק בזמן ההידור.

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

מסתבר שהקומפיילר יכול ליצור קוד נכון בלי לשמור את הסוגים הגנריים עד זמן הריצה. אבל זה כן אומר שלפעמים אתם עושים משהו, כמו is בדיקות של סוגים גנריים, שהקומפיילר לא יכול לתמוך בו. לכן, ב-Kotlin נוספו סוגים מוחשיים (reified) או אמיתיים.

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

השיעור הזה התמקד בגנריות, שהן חשובות כדי שהקוד יהיה גמיש יותר וקל יותר לשימוש חוזר.

  • כדאי ליצור מחלקות גנריות כדי שהקוד יהיה גמיש יותר.
  • אפשר להוסיף אילוצים גנריים כדי להגביל את הסוגים שמשמשים עם גנריים.
  • אפשר להשתמש בסוגים in ו-out עם גנריקות כדי לספק בדיקת סוגים טובה יותר, וכך להגביל את הסוגים שמועברים למחלקות או מוחזרים מהן.
  • יצירת פונקציות ושיטות גנריות לעבודה עם סוגים גנריים. לדוגמה:
    fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) { ... }
  • שימוש בפונקציות כלליות של תוספים כדי להוסיף פונקציונליות לא בסיסית לכיתה.
  • לפעמים יש צורך בסוגים מוחשיים בגלל מחיקת סוגים. סוגים מוחשיים, בניגוד לסוגים גנריים, נשמרים עד זמן הריצה.
  • כדי לוודא שהקוד פועל כמו שצריך, משתמשים בפונקציה check(). לדוגמה:
    check(!waterSupply.needsProcessing) { "water supply needs processing first" }

תיעוד של Kotlin

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

מדריכים ל-Kotlin

באתר https://try.kotlinlang.org יש הדרכות מפורטות שנקראות Kotlin Koans, מפרש מבוסס-אינטרנט וסט מלא של מסמכי עזר עם דוגמאות.

קורס של Udacity

כדי לצפות בקורס של Udacity בנושא הזה, אפשר לעבור אל Kotlin Bootcamp for Programmers.

IntelliJ IDEA

מסמכי התיעוד של IntelliJ IDEA זמינים באתר JetBrains.

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

  • אם צריך, מקצים שיעורי בית.
  • להסביר לתלמידים איך להגיש מטלות.
  • בודקים את שיעורי הבית.

אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.

אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.

עונים על השאלות

שאלה 1

מהי המוסכמה למתן שם לסוג גנרי?

<Gen>

<Generic>

<T>

<X>

שאלה 2

הגבלה על הסוגים שמותרים לסוג כללי נקראת:

‫▢ הגבלה גנרית

‫▢ אילוץ גנרי

▢ הבהרה

‫▢ מגבלה גנרית על סוג

שאלה 3

הגדרה של 'מוחשי':

‫▢ חושבה ההשפעה האמיתית של אובייקט על הביצוע.

‫▢ הוגדר אינדקס מוגבל של רשומות בכיתה.

‫▢ פרמטר הסוג הגנרי הפך לסוג אמיתי.

‫▢ הופעל אינדיקטור לשגיאה מרחוק.

עוברים לשיעור הבא: 6. מניפולציה פונקציונלית

סקירה כללית של הקורס, כולל קישורים ל-Codelabs אחרים, זמינה במאמר "Kotlin Bootcamp for Programmers: Welcome to the course".