یادگیری زبان برنامه‌نویسی Kotlin

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 به استنباط نوع به شما هم اختصار و هم ایمنی نوع می‌دهد.

ایمنی تهی

در برخی‌از زبان‌ها، متغیر نوع مرجع را می‌توان بدون ارائه مقدار صریح اولیه تعریف کرد. در این موارد، متغیرها معمولاً حاوی مقدار تهی هستند. متغیرهای Kotlin به‌طور پیش‌فرض نمی‌توانند مقادیر تهی را نگه دارند. این یعنی کد آماده زیر نامعتبر است:

// Fails to compile
val languageName: String = null

برای اینکه متغیری مقدار تهی را نگه دارد، باید از نوع تهی‌پذیر باشد. می‌توانید با افزودن پسوند ? به نوع متغیر، آن را به‌عنوان متغیر تهی‌پذیر مشخص کنید، همان‌طور که در مثال زیر نشان داده شده است:

val languageName: String? = null

با نوع String?، می‌توانید مقدار String یا null را به languageName اختصاص دهید.

باید با متغیرهای تهی‌پذیر با دقت رفتار کنید وگرنه با NullPointerException وحشتناکی مواجه خواهید شد. برای مثال، در جاوا، اگر بخواهید روشی را روی مقدار تهی فراخوانی کنید، برنامه‌تان خراب می‌شود.

‫Kotlin چندین سازوکار برای کار ایمن با متغیرهای تهی‌پذیر ارائه می‌دهد. برای کسب اطلاعات بیشتر، به الگوهای رایج Kotlin در Android: قابلیت تهی بودن مراجعه کنید.

شرطی‌ها

‫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 است. در این مثال، مقدار اولیه از نتیجه عبارت if-else به answerString اختصاص داده می‌شود. از استنباط نوع می‌توان برای حذف کردن تعریف نوع صریح برای answerString استفاده کرد، اما اغلب بهتر است برای وضوح بیشتر آن را اضافه کنید.

با افزایش پیچیدگی عبارت شرطی، ممکن است بخواهید عبارت if-else را با عبارت when جایگزین کنید، همان‌طور که در مثال زیر نشان داده شده است:

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

println(answerString)

هر شاخه در عبارت when با یک شرط، یک پیکان (->)، و یک نتیجه نشان داده می‌شود. اگر شرط سمت راست پیکان به‌درستی ارزیابی شود، نتیجه عبارت سمت چپ برگردانده می‌شود. توجه داشته باشید که اجرا از یک شاخه به شاخه بعدی منتقل نمی‌شود. کد موجود در مثال عبارت when ازنظر عملکردی معادل کد موجود در مثال قبلی است اما خواندن آن احتمالاً آسان‌تر است.

شرط‌های Kotlin یکی از ویژگی‌های قدرتمند آن، یعنی تبدیل هوشمند را برجسته می‌کند. به‌جای استفاده از عملگر فراخوانی ایمن یا عملگر ادعای غیرتهی برای کار با مقادیر تهی‌پذیر، می‌توانید بااستفاده از گزاره شرطی بررسی کنید که آیا متغیری حاوی ارجاع به مقدار تهی است یا نه، همان‌طور که در مثال زیر نشان داده شده است:

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

در شاخه شرطی، languageName ممکن است به‌عنوان غیرقابل تهی درنظر گرفته شود. ‫Kotlin به‌اندازه کافی هوشمند است که تشخیص دهد شرط اجرای شاخه این است که languageName مقدار تهی نداشته باشد، بنابراین لازم نیست languageName را در آن شاخه به‌عنوان تهی‌پذیر درنظر بگیرید. این پخش هوشمند برای بررسی‌های null، بررسی‌های نوع، یا هر شرطی که یک قرارداد را برآورده کند کار می‌کند.

توابع

می‌توانید یک یا چند عبارت را در یک تابع گروه‌بندی کنید. به‌جای اینکه هر بار که به نتیجه‌ای نیاز دارید همان مجموعه عبارت را تکرار کنید، می‌توانید عبارت‌ها را در تابعی بپیچید و به‌جای آن تابع را فراخوانی کنید.

برای تعریف تابع، از کلیدواژه 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 می‌گیرد. در تابع، می‌توانید بااستفاده از نام آرگومان به آن ارجاع دهید.

هنگام فراخوانی این تابع، باید یک آرگومان در پرانتزهای فراخوانی تابع قرار دهید:

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 نشان داده می‌شود. بااین‌حال، این کد تابع را فراخوانی نمی‌کند. برای بازیابی نتیجه تابع، باید آن را مانند تابع نام‌گذاری‌شده فراخوانی کنید. هنگام تماس با stringLengthFunc باید String را ارائه کنید، همان‌طور که در مثال زیر نشان داده شده است:

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
})

اگر تابع ناشناس آخرین پارامتر تعریف‌شده در تابع باشد، می‌توانید آن را خارج از پرانتزهای استفاده‌شده برای فراخوانی تابع ارسال کنید، همان‌طور که در مثال زیر نشان داده شده است:

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

توابع ناشناس را می‌توانید در سراسر کتابخانه استاندارد Kotlin پیدا کنید. برای اطلاعات بیشتر، توابع مرتبه بالاتر و لامبداها را ببینید.

کلاس‌ها

همه انواع ذکرشده تاکنون در زبان برنامه‌نویسی Kotlin ساخته شده‌اند. اگر می‌خواهید نوع سفارشی خودتان را اضافه کنید، می‌توانید کلاسی را بااستفاده از کلیدواژه class تعریف کنید، همان‌طور که در مثال زیر نشان داده شده است:

class Car

مشخصات

کلاس‌ها وضعیت را بااستفاده از ویژگی‌ها نشان می‌دهند. دارایی متغیری در سطح کلاس است که می‌تواند شامل یک دریافت‌کننده، یک تنظیم‌کننده، و یک فیلد پشتیبان باشد. ازآنجایی‌که خودرو برای حرکت کردن به چرخ نیاز دارد، می‌توانید فهرستی از Wheel شیء را به‌عنوان دارایی Car اضافه کنید، همان‌طور که در مثال زیر نشان داده شده است:

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

توجه داشته باشید که wheels یک public val است، یعنی wheels را می‌توان از خارج از کلاس Car نیز دردسترس قرار داد و نمی‌توان آن را دوباره اختصاص داد. اگر می‌خواهید نمونه‌ای از Car را دریافت کنید، ابتدا باید سازنده آن را فراخوانی کنید. از آنجا می‌توانید به هریک از دارایی‌های دسترس‌پذیر آن دسترسی پیدا کنید.

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

اگر می‌خواهید چرخ‌هایتان را سفارشی‌سازی کنید، می‌توانید سازنده سفارشی‌ای تعریف کنید که نحوه مقداردهی اولیه ویژگی‌های کلاس را مشخص می‌کند:

class Car(val wheels: List<Wheel>)

در مثال بالا، سازنده کلاس List<Wheel> را به‌عنوان متغیر سازنده می‌گیرد و از آن متغیر برای مقداردهی اولیه ویژگی wheels استفاده می‌کند.

توابع کلاس و کپسوله‌سازی

کلاس‌ها از توابع برای مدل‌سازی رفتار استفاده می‌کنند. توابع می‌توانند وضعیت را تغییر دهند و به شما کمک کنند فقط داده‌هایی را که می‌خواهید نمایان کنید. این کنترل دسترسی بخشی از مفهوم شیءگرای بزرگ‌تری است که به‌عنوان کپسوله‌سازی شناخته می‌شود.

در مثال زیر، دارایی 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
    }
}

اگر می‌خواهید نحوه ارجاع به دارایی را سفارشی‌سازی کنید، می‌توانید واگیرنده و تنظیم‌کننده سفارشی ارائه دهید. برای مثال، اگر می‌خواهید getter دارایی را آشکار کنید و درعین‌حال دسترسی به setter آن را محدود کنید، می‌توانید آن setter را به‌عنوان 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 به بایت‌کد JVM کامپایل می‌شود، کد Kotlin شما می‌تواند مستقیماً کد جاوا را فراخوانی کند و بالعکس. این یعنی می‌توانید مستقیماً از کتابخانه‌های جاوا موجود در Kotlin استفاده کنید. علاوه‌براین، اکثر APIهای Android به زبان Java نوشته شده‌اند و می‌توانید آن‌ها را مستقیماً از Kotlin فراخوانی کنید.

مراحل بعدی

‫Kotlin زبانی انعطاف‌پذیر و عمل‌گرا است که پشتیبانی و شتاب آن درحال افزایش است. اگر هنوز آن را امتحان نکرده‌اید، شما را تشویق می‌کنیم که آن را امتحان کنید. برای مراحل بعدی، به اسناد رسمی Kotlin و همچنین راهنمای نحوه اعمال الگوهای رایج Kotlin در برنامه‌های Android نگاهی بیندازید.