ללמוד את שפת התכנות Kotlin

קוטלין היא שפת תכנות בשימוש נפוץ על ידי מפתחי Android בכל מקום. הנושא הזה משמש כ-Kotlin שיעזרו לכם להתחיל לעבוד במהירות.

הצהרה לגבי משתנים

Kotlin משתמשים בשתי מילות מפתח שונות כדי להצהיר על משתנים: val ו-var.

  • משתמשים בפונקציה val למשתנה שהערך שלו אף פעם לא משתנה. לא ניתן להקצות מחדש ערך למשתנה שהוצהר באמצעות val.
  • משתמשים בפונקציה var למשתנה שהערך שלו יכול להשתנות.

בדוגמה הבאה, count הוא משתנה מסוג Int שמוקצה לו הערך הראשוני של 10:

var count: Int = 10

Int הוא סוג שמייצג מספר שלם, אחד מסוגי המספרים הרבים שאפשר לייצג ב-Kotlin. בדומה לשפות אחרות, אפשר להשתמש גם ב- Byte, Short, Long, Float ו-Double בהתאם לנתונים המספריים שלך.

המשמעות של מילת המפתח var היא שאפשר להקצות מחדש ערכים ל-count לפי הצורך. עבור לדוגמה, אפשר לשנות את הערך של count מ-10 ל-15:

var count: Int = 10
count = 15

עם זאת, יש ערכים שלא אמורים להשתנות. כדאי לשקול String בשם languageName. כדי לוודא שהשדה languageName תמיד מכיל ערך של "Kotlin", לאחר מכן אפשר להצהיר על languageName באמצעות מילת המפתח val:

val languageName: String = "Kotlin"

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

סוג ההסקה

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

מכיוון שהערך של "Kotlin" הוא מסוג String, המהדר מסיק languageName הוא גם String. שימו לב ש-Kotlin הוא סוג סטטי בשפת היעד. זה אומר שהסוג מסתיים בזמן הידור שינויים.

בדוגמה הבאה, languageName נחשב ל-String, כך שלא ניתן מפעילים פונקציה שאינה חלק מהמחלקה String:

val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

toUpperCase() היא פונקציה שניתן לקרוא לה רק על משתנים מסוג String. מכיוון שהמהדר של Kotlin הסיק ש-languageName הוא String, אפשר להתקשר בבטחה אל toUpperCase(). עם זאת, inc() הוא אופרטור Int ולכן לא ניתן לקרוא לה ב-String. הגישה של Kotlin להקלדה ההֶקֵּשׁ שלכם מספק גם תמציתיות וגם בטיחות בסוג.

בטיחות אפס

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

// Fails to compile
val languageName: String = null

כדי שמשתנה יהיה ערך null, הוא צריך להיות מסוג null. אפשר לציין משתנה כניתן ל-null על ידי סיומת הסוג שלו ב-?, כפי שמוצג בדוגמה הבאה:

val languageName: String? = null

בסוג String?, אפשר להקצות ערך String או null languageName

עליך לטפל במשתנים שאינם יכולים להיות זהירים או לסכן אותם NullPointerException ב-Java, לדוגמה, אם מנסים להפעיל method עם ערך null, התוכנית שלך קורסת.

Kotlin מספקת מספר מנגנונים לעבודה בטוחה עם ערכי null משתנים. מידע נוסף זמין במאמר הבא: דפוסי Kotlin נפוצים ב-Android: Nullability.

מותנים

ב-Kotlin יש כמה מנגנונים להטמעת לוגיקה מותנית. במידה הרבה ביותר אחת מהאפשרויות הבאות היא הצהרת if-else. אם ביטוי מופיע בתוך סוגריים ליד מילת מפתח if מחזירה true, והקוד בתוך ההסתעפות הזאת (כלומר, הקוד שמופיע מיד אחרי עטוף מתולתל). סוגריים מסולסלים). אחרת, הקוד בהסתעפות else יריץ.

if (count == 42) {
    println("I have the answer.")
} else {
    println("The answer eludes me.")
}

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

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

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

val answerString: String = if (count == 42) {
    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

במרומז, כל הסתעפות מותנית מחזירה את התוצאה של הביטוי השורה האחרונה, כך שלא צריך להשתמש במילת מפתח return. בגלל שהתוצאה של כל שלוש ההסתעפויות הן מסוג String, התוצאה של הביטוי if-else היא גם מסוג String. בדוגמה הזו, ל-answerString מוקצית האות מהתוצאה של הביטוי if-else. אפשר להשתמש בסוג ההֶקֵּשׁ, השמטת את הצהרת הסוג המפורשת לגבי answerString, אבל בדרך כלל מומלץ לכלול אותה לשם הבהרה.

ככל שהמורכבות של ההצהרה המותנית גדלה, כדאי לנסות להחליף את הביטוי 'if-else' בביטוי מתי, כמו שהוא בדוגמה הבאה:

val answerString = when {
    count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
}

println(answerString)

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

התנאים של Kotlin מדגישים את אחת מהתכונות העוצמתיות יותר, העברה חכמה. במקום להשתמש באופרטור החיפוש הבטוח או באופרטור not-null את האופרטור 'טענת נכוֹנוּת' (assertion) כדי לעבוד עם ערכים שהם יכולים להיות null, ובמקום זאת אפשר לבדוק מכיל הפניה לערך null באמצעות הצהרה מותנית, שמוצגת בדוגמה הבאה:

val languageName: String? = null
if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

בתוך ההסתעפות המותנה, ניתן להתייחס אל languageName כאל ערך null. Kotlin חכם מספיק כדי לזהות את התנאי לביצוע ההסתעפות. הוא ש-languageName לא מכיל ערך null, אז לא צריך להתייחס הערך languageName יכול להיות null בתוך הסתעפות הזו. ההעברה החכמה הזו פועלת במצב null בדיקות, type checks, או כל תנאי שעונה על חוזה.

פונקציות

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

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

בהמשך לדוגמאות הקודמות, הנה פונקציית Kotlin מלאה:

fun generateAnswerString(): String {
    val answerString = if (count == 42) {
        "I have the answer."
    } else {
        "The answer eludes me"
    }

    return answerString
}

השם של הפונקציה בדוגמה שלמעלה הוא generateAnswerString. הוא לא מקבל קלט. הפלט שלה הוא מסוג String. כדי להתקשר באמצעות השם שלה ואחריו אופרטור ההפעלה (()). ב בדוגמה הבאה, המשתנה answerString מאותחל עם התוצאה של generateAnswerString().

val answerString = generateAnswerString()

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

fun generateAnswerString(countThreshold: Int): String {
    val answerString = if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }

    return answerString
}

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

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

val answerString = generateAnswerString(42)

פישוט של הצהרות על פונקציות

generateAnswerString() היא פונקציה פשוטה למדי. הפונקציה מצהירה ואז חוזר מיד. כשהתוצאה של ביטוי יחיד היא שמוחזר מפונקציה, אפשר לדלג על הצהרה על משתנה מקומי באופן ישיר מחזירה את התוצאה של הביטוי if-else שנכלל בפונקציה, כפי שמוצגת בדוגמה הבאה:

fun generateAnswerString(countThreshold: Int): String {
    return if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }
}

אפשר גם להחליף את מילת המפתח החוזרת באופרטור ההקצאה:

fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
        "I have the answer"
    } else {
        "The answer eludes me"
    }

פונקציות אנונימיות

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

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

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

בדוגמה שלמעלה, stringLengthFunc מכיל הפניה לקובץ אנונימי פונקציה שמקבלת את הערך String כקלט ומחזירה את אורך הקלט String כפלט מסוג Int. לכן סוג הפונקציה הוא מסומן בתור (String) -> Int. עם זאת, הקוד הזה לא מפעיל את הפונקציה. כדי לאחזר את התוצאה של הפונקציה, צריך להפעיל אותה כמו פונקציה בעלת שם. חובה לספק String כשמתקשרים אל stringLengthFunc, כמו שמוצגת בדוגמה הבאה:

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

val stringLength: Int = stringLengthFunc("Android")

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

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

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

fun stringMapper(str: String, mapper: (String) -> Int): Int {
    // Invoke function
    return mapper(str)
}

הפונקציה stringMapper() לוקחת את String עם פונקציה נגזרת ערך Int מ-String שאתה מעביר אליו.

כדי לקרוא ל-stringMapper(), אפשר להעביר את String ואת הפונקציה תואם לפרמטר הקלט השני, כלומר פונקציה שמקבלת את הערך String כקלט ומפיק Int, כמו בדוגמה הבאה:

stringMapper("Android", { input ->
    input.length
})

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

stringMapper("Android") { input ->
    input.length
}

פונקציות אנונימיות קיימות בספרייה הרגילה של Kotlin. עבור מידע נוסף: פונקציות בהזמנה גבוהה יותר ו-Lambdas

שיעורים

כל הסוגים שהוזכרו עד עכשיו מובנים בתכנות Kotlin בשפת היעד. כדי להוסיף סוג מותאם אישית משלך, אפשר להגדיר כיתה באמצעות מילת המפתח class, כפי שמוצג בדוגמה הבאה:

class Car

מאפיינים

מחלקות מייצגות מצב באמצעות מאפיינים. א' property הוא משתנה ברמת המחלקה שיכול לכלול מקש getter, רכיב מגדיר (Setter) ושדה גיבוי (backup withholding). יש ברכב צורך בגלגלים כדי לנסוע, לכן אפשר להוסיף רשימה של Wheel אובייקטים של Car, כמו בדוגמה הבאה:

class Car {
    val wheels = listOf<Wheel>()
}

חשוב לשים לב ש-wheels הוא public val, כלומר אפשר לגשת ל-wheels מ- מחוץ לכיתה Car, ואי אפשר להקצות אותה מחדש. אם רוצים לקבל של Car, קודם צריך לקרוא ל-constructor שלו. בדף הזה אתם יכולים לגשת לכל אחד מהנכסים הנגישים שלו.

val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car

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

class Car(val wheels: List<Wheel>)

בדוגמה שלמעלה, ה-constructor של המחלקה לוקח את List<Wheel> בתור של constructor ומשתמשת בארגומנט הזה כדי לאתחל את ה-wheels שלו לנכס.

פונקציות Class וencapsulation

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

בדוגמה הבאה, הנכס doorLock נשאר פרטי מכל דבר מחוץ לכיתה Car. כדי לפתוח את הרכב, צריך להתקשר למוקד של unlockDoor() הפונקציה מעבירה מפתח חוקי, כמו בדוגמה הבאה:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

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

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15
        private set

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

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

יכולת פעולה הדדית

אחת התכונות החשובות ביותר של Kotlin היא יכולת הפעולה ההדדית הנוזלית שלו עם Java. מכיוון שהקוד של Kotlin עובר הידור של bytecode של JVM, קוד Kotlin שלך יכול לקרוא ישירות לקוד Java ולהיפך. כלומר, אתם יכולים למנף קיימות של ספריות Java ישירות מ-Kotlin. בנוסף, רוב ממשקי API של Android נכתבים ב-Java, וניתן לקרוא להם ישירות מ-Kotlin.

השלבים הבאים

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