برنامه نویسی 3408 بازدید

کاتلین یک زبان نسبتاً جدید برنامه‌نویسی است که از سوی JetBrains برای اپلیکیشن‌های مدرن چندپلتفرمی توسعه یافته است. امروزه کاتلین کاربرد گسترده‌ای برای توسعه اندروید دارد و جایگزین جاوا شده است. دلیل این امر آن است که زبان کاتلین امن و منسجم است و خواندن و نوشتن کد کاتلین آسان است. در این مقاله جامع به صورت تفصیلی به آموزش کاتلین خواهیم پرداخت.

فهرست مطالب این نوشته

ویژگی‌های زبان برنامه‌نویسی کاتلین

زبان کاتلین تحت لایسنس آپاچی نسخه 2.0 توزیع یافته است. کامپایلر این زبان، پلاگین IntelliJ IDEA و همچنین بهینه‌سازی‌های صورت گرفته روی کتابخانه‌های ابتدایی جاوا و ابزارهای Build همگی متن-باز هستند.

کاتلین جایگزین جاوا و اندروید می‌شود: زبان برنامه‌نویسی کاتلین 100% امکان جایگزینی جاوا و اندروید را دارد. این بدان معنی است که همه کد‌های کنونی جاوا/اندروید شما می‌توانند به صورت کامل به زبان کاتلین نوشته شوند.

منسجم و گویا است: بر اساس برخی تخمین‌های اولیه، کدنویسی با کاتلین موجب می‌شود که تا حدود 40% از حجم کد در قیاس با زبان جاوا کاسته شود. معنی گویا بودن کاتلین هم این است که نوشتن کد به این زبان، به طوری که هم انسان و هم کامپایلر، آن را به سهولت درک کنند، کار آسانی است.

یادگیری کاتلین آسان است: اگر از قبل با زبان‌های برنامه‌نویسی دیگر مانند جاوا، اسکالا، گرووی، سی شارپ، جاوا اسکریپت و Gosu آشنا باشید، یادگیری کاتلین برای شما آسان خواهد بود.

کاتلین ابزارهای فروانی دارد: کاتلین از سوی JetBrains توسعه یافته است. این شرکت به خاطر توسعه ابزارهای برنامه‌نویسی برای افراد حرفه‌ای مشهور است. از این رو عجیب نیست که ابزارهای فراوانی برای زبان کاتلین عرضه شده است.

کاتلین امن است: کاتلین یک زبان نوع‌بندی استاتیک است. از این رو بررسی نوع در زمان کامپایل و نه زمان اجرا انجام می‌گیرد و باگ‌های ساده در همین مرحله به دام می‌افتند.

زبان برنامه‌نویسی کاتلین می‌تواند روی ماشین مجازی جاوا (JVM) اجرا شود. این زبان ترکیبی از پارادایم‌های برنامه‌نویسی شیءگرا و تابعی را در یک پلتفرم نامحدود، خودکفا و متمایز ترکیب کرده است.

تاریخچه زبان برنامه نویسی کاتلین

  • در سال 2016 نخستین نسخه کاتلین یعنی Kotlin v1.0 عرضه شد. در سال 2017 گوگل اعلام کرد که در زمینه اندروید پشتیبانی دست اولی از زبان کاتلین به عمل می‌آورد.
  • در سال 2018 کاتلین نسخه 1.2 به همراه امکان توزیع کد بین JVM و جاوا اسکریپت عرضه شد.
  • در سال 2019 گوگل اعلام کرد که کاتلین زبان برنامه‌نویسی ترجیحی این شرکت برای توسعه اپلیکیشن‌های اندرویدی محسوب می‌شود.

تنظیم محیط توسعه کاتلین

در این بخش مراحل راه‌اندازی محیط کاری را با نصب کاتلین توضیح می‌دهیم.

نصب جاوا

چنان که پیش‌تر اشاره کردیم کاتلین بر مبنای جاوا طراحی شده است. از این رو در صورتی که می‌خواهید از کاتلین استفاده کنید، ابتدا باید جاوا را نصب کنید.

نصب IDE-های جاوا

خوشبختانه IDE-های متعددی برای جاوا وجود دارند. به منظور توسعه کد کاتلین می‌توانید از یکی از IDE-های Eclipse ،NetBeans یا IntelliJ IDEA استفاده کنید. ما در این راهنما از ایکلیپس استفاده می‌کنیم.

نصب کاتلین

برای نصب ایکلیپس در کاتلین باید به بخش Help ایکلیپس بروید و روی گزینه Eclipse Marketplace کلیک کنید.

آموزش کاتلین

اکنون کلیدواژه Kotlin را در کار جستجو وارد کنید. با کلیک روی کلمه Go لیستی از پلاگین‌ها نمایان می‌شود. در این بخش باید یک پلاگین برای کاتلین ببینید. با کلیک روی این لینک، پلاگین کاتلین برای ایکلیپس را نصب کنید.

آموزش کاتلین

IDE ایکلیپس را ری‌استارت کنید، زمانی که نصب کامل شد، می‌توانید یک آیکون میانبر در گوشه راست-بالای IDE ایکلیپس ببینید. این یک روش دسترسی سریع است.

آموزش کاتلین

روش دیگر برای دسترسی به کاتلین در IDE ایکلیپس این است که به منوی Windows> Perspectives>Open Perspectives بروید و گزینه Others را انتخاب کنید. در این بخش می‌توانید فهرستی از پلاگین‌های نصب شده اخیر را مانند تصویر زیر ببینید:

آموزش کاتلین

زمانی که نصب کاتلین پایان یافت، می‌توانیم نخستین برنامه کاتلین خود را بنویسیم.

نوشتن نخستین برنامه کاتلین

کار خود را با نخستین پروژه کاتلین آغاز می‌کنیم. به این منظور از منوی File گزینه New را انتخاب کرده و سپس با انتخاب Others یک پروژه کاتلین از فهرست ارائه شده ایجاد کنید.

آموزش کاتلین

اینک باید یک نام برای پروژه وارد کنید تا بتوانید شروع به کدنویسی کاتلین بکنید.

آموزش کاتلین

چنان که دیدید، با طی این مراحل ساده و دانلود نصب ایکلیپس و پلاگین کاتلین روی سیستم می‌توانید اقدام به برنامه‌نویسی کاتلین بکنید.

برنامه Hello, World

برنامه «!Hello, World» به یک برنامه ساده گفت می‌شود که خروجی !Hello, World را روی صفحه نمایش می‌دهد. از آنجا که این یک برنامه ساده است، غالباً برای معرفی یک زبان جدید برنامه‌نویسی مورد استفاده قرار می‌گیرد. در این بخش با نوشتن یک برنامه !Hello, World با ساختار و چارچوب زبان کاتلین آشنا می‌شویم:

// Hello World Program

fun main(args : Array<String>) {
    println("Hello, World!")
}

زمانی که برنامه فوق را اجرا کنید، خروجی زیر را مشاهده می‌کنید:

Hello, World!

طرز کار برنامه !Hello, World با زبان برنامه‌نویسی کاتلین چگونه است؟

کد موجود در خط نخست این برنامه به صورت زیر است:

// Hello World Program

در زبان کاتلین هر خطی که با دو علامت پشت سرهم ممیز (//) آغاز شود، به معنی توضیح یا کامنت است. این کامنت‌ها از سوی کامپایلر نادیده گرفته می‌شوند. هدف از نوشتن کامنت درک بهتر برنامه‌نویس‌ها از کد است و بدین ترتیب مقصود و کارکرد برنامه تشریح می‌شود.

خط دوم برنامه ما به این صورت است:

fun main(args: Array<String>) { ... }

این تابع اصلی (main) برنامه است که وجود آن در هر اپلیکیشن کاتلین ضروری است. کامپایلر کاتلین اجرای کد را از تابع main آغاز می‌کند.

این تابع یک آرایه از رشته‌ها به عنوان پارامتر می‌گیرد و یک Unit بازگشت می‌دهد. در مورد تابع‌ها و پارامتر‌های آن در کاتلین در بخش‌های بعدی بیشتر توضیح خواهیم داد.

فعلاً به خاطر داشته باشید که تابع main یک تابع الزامی است که نقطه ورودی هر برنامه کاتلین محسوب می‌شود. امضای تابع main به صورت زیر است:

fun main(args : Array<String>) {
    ... .. ...
}

خط سوم برنامه به صورت زیر است:

println("Hello, World!")

تابع ()println پیام مورد نظر را درون علامت گیومه پرینت کرده و یک کاراکتر newline به استریم خروجی استاندارد اضافه می‌کند. در این برنامه عبارت !Hello, World و یک خط جدید در خروجی چاپ می‌شود.

مقایسه با برنامه Hello, World در جاوا

چنان که قبلاً اشاره کردیم، کاتلین به صورت 100% قابلیت جایگزین کردن جاوا را دارد. معادل برنامه Hello, World در زبان جاوا به صورت زیر است:

// Hello World Program

class HelloWorldKt {
    public static void main(String[] args) {
        System.out.println("Hello, World!"); 
    }
}

چند نکته مهم

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

اگر از IntelliJ IDEA استفاده می‌کنید، با مراجعه به منوی Run > Edit Configurations می‌توانید این کلاس را ببینید. اگر نام فایل کاتلین به صورت HelloWorld.kt باشد، کامپایلر کلاس HelloWorldKt را برای شما ایجاد می‌کند.

آموزش کاتلین

تابع ()println به صورت داخلی ()System.out.println را فراخوانی می‌کند.

اگر از IntelliJ IDEA استفاده می‌کنید، کرسر ماوس خود را در کنار عبارت println قرار دهید و به منوی Navigate > Declaration بروید. همچنین می‌توانید کلیدهای ترکیبی Ctrl+B (در مک: Cmd+B) را بزنید. به این ترتیب فایل اعلان Console.kt باز می‌شود. در این فایل می‌توان دید که تابع ()println به صورت داخلی ()System.out.println را فراخوانی‌ می‌کند.

آموزش کاتلین

متغیرهای کاتلین و انواع ابتدایی

چنان که می‌دانید، متغیر در یک برنامه به مکانی از حافظه گفته می‌شود که داده‌ها را در خود نگهداری می‌کند. برای مشخص ساختن این ناحیه ذخیره‌سازی، هر متغیر باید یک نام یکتا (شناسه) داشته باشد.

شیوه اعلان متغیر در کاتلین

برای اعلان یک متغیر در کاتلین می‌توان از کلیدواژه var یا val استفاده کرد. به مثال زیر توجه کنید:

var language = "French"
val score = 95

اختلاف بین var و val در ادامه این بخش توضیح داده شده است. فعلاً روی شیوه اعلان متغیر تمرکز می‌کنیم. در کد فوق language یک متغیر از نوع String و Score نیز متغیری با نوع Int است. در کاتلین لزومی به اعلام صریح نوع متغیر وجود ندارد و این زبان خودش ‌می‌تواند نوع متغیر را برای شما مشخص بکند. کامپایلر خودش تشخیص می‌دهد که “French” یک رشته (String) و 95 یک عدد صحیح (Int) است. این قابلیت در زبان‌های برنامه‌نویسی به نام «استنباط نوع» (type inference) شناخته می‌شود.

با این حال، در صورت تمایل می‌توانید نوع یک متغیر را به صورت صریح نیز تعیین کنید:

var language: String = "French"
val score: Int = 95

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

var language: String      // variable declaration of type String
... .. ...
language = "French"       // variable initialization

val score: Int          // variable declaration of type Int 
... .. ...
score = 95             // variable initialization

اما مثال زیر موجب بروز خطا می‌شود:

var language// Error
language = "French"

در مثال فوق، نوع متغیر language به صورت صریحی مشخص نشده است. همچنین متغیر نیز درون گزاره اعلان، مقداردهی نشده است.

var language: String
language = 14// Error

در مثال فوق نیز تلاش کرده‌ایم که مقدار 14 (عدد صحیح) را به متغیری از نوع متفاوت (String) نسبت دهیم که موجب بروز خطا شده است.

تفاوت بین var و val

  • Val (ارجاع تغییرناپذیر) – متغیری که با استفاده از کلیدواژه val اعلان شود، پس از این که مقداری به آن انتساب یافت، دیگر نمی‌تواند تغییر داده شود. این وضعیت شبیه متغیر final در جاوا است.
  • Var (ارجاع تغییرپذیر) – متغیری که با کلیدواژه var اعلان شود، می‌تواند در ادامه برنامه مقدار متفاوتی بگیرد. این کلیدواژه معادل متغیرهای معمولی جاوا است.

به مثال‌های زیر توجه کنید:

var language = "French"
language = "German"

در مثال فوق متغیر language پس از مقدار‌دهی اولیه، در ادامه مقدار German را گرفته است. از آنجا که این متغیر با استفاده از کلیدواژه var اعلان یافته است، این کد به درستی کار می‌کند.

val language = "French"
language = "German"// Error

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

انواع ابتدایی کاتلین

کاتلین یک زبان با نوع‌بندی استاتیک مانند جاوا است. معنی این حرف آن است که نوع یک متغیر در زمان کامپایل مشخص می‌شود. به مثال زیر توجه کنید:

val language: Int
val marks = 12.3

در مثال فوق، کامپایلر پیش از کامپایل کردن کد می‌داند که language دارای نوع Int است و marks از نوع Double است.

انواع داخلی داده‌ها در زبان کاتلین به صورت زیر دسته‌بندی می‌شوند:

  • اعداد
  • کاراکترها
  • مقادیر بولی
  • آرایه‌ها

نوع عددی

اعداد در کاتلین انواعی مشابه زبان جاوا دارند. شش نوع داده داخلی در کاتلین وجود دارد که اعداد را نمایش می‌دهند:

  • Byte
  • Short
  • Int
  • Long
  • Float
  • Double

نوع Byte

نوع داده byte مقادیری درباره 128- تا 127 می‌گیرد. این نوع معادل عدد صحیح مکمل دوی هشت بیتی علامت‌دار است. از این نوع داده در مواردی که عدد در بازه 128 -تا 127 قرار می‌گیرد، به جای نوع Int یا دیگر انواع داده صحیح برای صرفه‌جویی در مصرف حافظه استفاده می‌شود.

مثال

fun main(args : Array<String>) {
    val range: Byte = 112
    println("$range")

    // The code below gives error. Why?
    // val range1: Byte = 200
}

زمانی که برنامه فوق اجرا شود، خروجی زیر چاپ می‌شود:

112

نوع Short

نوع داده Short می‌تواند مقادیری بین 32678- تا 32677 داشته باشد که معادل عدد صحیح مکمل دوی 16 بیتی علامت‌دار است. در صورتی که مطمئن هستید مقدار یک متغیر در بازه [32767, 32768-] قرار دارد، می‌توانید از این نوع داده به جای انواع داده دیگر استفاده کنید.

مثال

fun main(args : Array<String>) {

    val temperature: Short = -11245
    println("$temperature")
}

زمانی که برنامه را اجرا کنید، خروجی زیر تولید می‌شود:

-11245

نوع Int

نوع داده Int مقادیری بین $$-2^{31}$$ تا $$2^{31}-1$$ می‌گیرد که معادل عدد صحیح مکمل دوی 2 بیتی علامت‌دار است.

مثال

fun main(args : Array<String>) {

    val score: Int =  100000
    println("$score")
}

زمانی که برنامه را اجرا کنید، خروجی به صورت زیر خواهد بود:

100000

اگر یک عدد صحیحی را بدون انتساب صریح نوع، بین $$-2^{31}$$ تا $$2^{31}-1$$ به یک متغیر انتساب دهید، این متغیر از نوع Int خواهد بود. به مثال زیر توجه کنید:

fun main(args : Array<String>) {

   // score is of type Int
    val score = 10
    println("$score")
}

اگر از IntelliJ IDEA استفاده می‌کنید، می‌توانید کرسر را روی متغیر قرار داده و با فشردن کلیدهای ترکیبی Ctrl+Shift+P نوع آن را ببینید.

آموزش کاتلین

نوع Long

نوع داده Long مقادیری بین $$-2^{63}$$ تا $$2^{63}-1$$ می‌گیرد که معادل عدد صحیح مکمل دوی 63 بیتی علامت‌دار است.

مثال

fun main(args: Array<String>)

fun main(args : Array<String>) {

    val highestScore: Long = 9999
    println("$highestScore")
}

زمانی که برنامه فوق را اجرا کنید، با خروجی زیر مواجه می‌شوید:

9999

اگر بدون تعیین صریح نوع یک متغیر مقداری بزرگ‌تر از $$-2^{31}$$ تا $$2^{31}-1$$ به یک متغیر بدهید، این متغیر به صورت خودکار به نوع Long تبدیل می‌شود. به مثال زیر توجه کنید:

val distance = 10000000000 // distance variable of type Long

به طور مشابه می‌توانید از حرف بزرگ L به صورت زیر برای تعیین نوع یک متغیر به صورت Long استفاده کنید:

val distance = 100L// distance value of type Long

نوع Double

نوع داده Double دارای دقت دو برابر اعشار 64 بیتی است.

مثال

fun main(args : Array<String>) {

    // distance is of type Double
    val distance = 999.5
    println("$distance")
}

زمانی که این برنامه را اجرا کنید، خروجی زیر تولید می‌شود:

999.5

نوع Float

نوع داده Float یک عدد اعشاری 32 بیتی با دقت منفرد است.

مثال

fun main(args : Array<String>) {

    // distance is of type Float
    val distance = 19.5F
    println("$distance")
}

خروجی برنامه فوق به صورت زیر است:

19.5

توجه کنید که ما به جای 19.5‌ مقدار 19.5F را در برنامه فوق داریم. دلیل این امر آن است که 19.5 یک مقدار لفظی Double است و نمی‌توان مقدار Double را به متغیر Float انتساب داد. برای این که به کامپایلر اعلام کنیم با عدد 19.5 به صورت Float برخورد کند، باید از حرف بزرگ F در انتهای آن استفاده کنیم.

اگر مطمئن نیستید یک متغیر در برنامه چه نوع عددی دریافت خواهد کرد، می‌توانید از نوع Number استفاده کنید. این نوع داده امکان انتساب هر دو نوع اعداد صحیح و اعشاری را به متغیر می‌دهد. به مثال زیر توجه کنید:

fun main(args : Array<String>) {

    var test: Number = 12.2
    println("$test")

    test = 12
    // Int smart cast from Number
    println("$test")

    test = 120L
    // Long smart cast from Number
    println("$test")
}

خروجی برنامه فوق به صورت زیر است:

12.2
12
120

نوع Char

برای نمایش یک کاراکتر در کاتلین از نوع داده Char استفاده می‌کنیم. برخلاف جاوا نوع داده Char در کاتلین می‌تواند به صورت عدد نیز استفاده شود.

fun main(args : Array<String>) {

    val letter: Char
    letter = 'k'
    println("$letter")
}

خروجی برنامه فوق به صورت زیر است:

k

در جاوا می‌توان مانند زیر عمل کرد:

char letter = 65;

با این حال کد زیر در کاتلین موجب تولید خطا می‌شود:

var letter: Char = 65 // Error

نوع Boolean

نوع داده Boolean دو مقدار به صورت True و False می‌تواند بگیرد.

مثال

fun main(args : Array<String>) {

    val flag = true
    println("$flag")
}

مقادیر بولی در گزاره‌های تصمیم‌گیری استفاده می‌شوند.

آرایه‌های کاتلین

یک آرایه در کاتلین داده‌ها (مقادیر) از یک نوع نگه‌داری می‌کند. برای نمونه می‌توانید یک آرایه ایجاد کنید که 100 مقدار از نوع Int را در خود ذخیره کند. آرایه‌ها در کاتلین به وسیله کلاس Array نمایش می‌یابند. این کلاس دارای تابع‌های get و set، مشخصه size و چند تابع عضو مفید دیگر است.

رشته‌های کاتلین

رشته‌ها در کاتلین به وسیله کلاس String نمایش می‌یابند. لفظ‌های رشته‌ای مانند “this is a string” به وسیله یک وهله از این کلاس پیاده‌سازی می‌شوند.

عملگرهای کاتلین

کاتلین یک مجموعه عملگرها دارد که برای اجرای عملیات حسابی انتسابی، مقایسه‌ای و غیره استفاده می‌شوند. عملگرها نمادهای خاصی هستند که روی عملوند‌های یک عملیات یعنی متغیرها و مقادیر اجرا می‌‌شوند. برای نمونه عملگر + برای جمع زدن اعداد مورد استفاده قرار می‌گیرد. در این بخش به بررسی عملگرهای کاتلین می‌پردازیم.

عملگرهای حسابی در کاتلین

فهرست عملگرهای حسابی و کارکرد آن‌ها در کاتلین به شرح زیر است:

عملگر توضیح
+ جمع (برای الحاق دو رشته نیز استفاده می‌شود.)
عملگر تفریق
* عملگر ضرب
/ عملگر تقسیم
% عملگر باقیمانده

مثال عملگرهای حسابی در کاتلین

fun main(args: Array<String>) {

    val number1 = 12.5
    val number2 = 3.5
    var result: Double

    result = number1 + number2
    println("number1 + number2 = $result")

    result = number1 - number2
    println("number1 - number2 = $result")

    result = number1 * number2
    println("number1 * number2 = $result")

    result = number1 / number2
    println("number1 / number2 = $result")

    result = number1 % number2
    println("number1 % number2 = $result")
}

خروجی برنامه فوق چنین است:

number1 + number2 = 16.0
number1 - number2 = 9.0
number1 * number2 = 43.75
number1 / number2 = 3.5714285714285716
number1% number2 = 2.0

عملگر + برای الحاق مقادیر string استفاده می‌شود.

مثالی از الحاق رشته‌ها

fun main(args: Array<String>) {

    val start = "Talk is cheap. "
    val middle = "Show me the code. "
    val end = "- Linus Torvalds"

    val result = start + middle + end
    println(result)
}

خروجی برنامه فوق به صورت زیر است:

Talk is cheap. Show me the code. - Linus Torvalds

طرز کار عملگرهای حسابی چگونه است؟

فرض کنید از عملگر + برای جمع زدن دو عدد a و b استفاده می‌کنیم. در پشت صحنه a + b تابع عضو a.plus(b) را فراخوانی می‌کند. عملگر plus طوری overload شده که با مقادیر String و دیگر انواع داده مقدماتی (به جز Char و Boolean) نیز کار کند.

// + operator for basic types
operator fun plus(other: Byte): Int
operator fun plus(other: Short): Int
operator fun plus(other: Int): Int
operator fun plus(other: Long): Long
operator fun plus(other: Float): Float
operator fun plus(other: Double): Double

// for string concatenation
operator fun String?.plus(other: Any?): String

همچنین می‌توانید از عملگر + و از طریق overload کردن تابع ()plus برای کار با انواع تعریف شده از سوی کاربر (مانند شیءها) استفاده کنید. در جدول زیر عملگرهای حسابی و تابع‌های متناظر آن‌ها ارائه شده است:

عبارت نام تابع ترجمه
a + b plus a.plus(b)
a – b minus a.minus(b)
a * b times a.times(b)
a / b div a.div(b)
a % b mod a.mod(b)

عملگرهای انتساب

عملگرهای انتساب برای نسبت دادن یک مقدار به متغیر استفاده می‌شوند. ما قبلاً نمونه‌ای از کاربرد عملگر انتساب ساده (=) را دیده‌ایم.

val age = 5

در مقال فوق مقدار 5 با استفاده از عملگر = به متغیر age انتساب می‌یابد. در ادامه فهرستی از همه عملگرهای انتساب و تابع‌های متناظر آن‌ها را می‌بینید:

عبارت معادل ترجمه
a +=b a = a + b a.plusAssign(b)
a -= b a = a – b a.minusAssign(b)
a *= b a = a * b a.timesAssign(b)
a /= b a = a / b a.divAssign(b)
a %= b a = a % b a.modAssign(b)

مثالی از عملگر‌های انتساب

fun main(args: Array<String>) {
    var number = 12

    number *= 5   // number = number*5
    println("number  = $number")
}

با اجرای کد فوق خروجی زیر به دست می‌آید:

number = 60

عملگرهای پیشوند یکانی (Unary prefix) و افزایش/کاهش در کاتلین

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

عملگر معنا عبارت ترجمه
+ جمع یکانی +a ()a.unaryPlus
منهای یکانی ( معکوس‌سازی علامت) -a ()a.unaryMinus
! نه (معکوس مقدار) !a ()a.not
++ افزایش به مقدار 1 واحد ++a ()a.inc
کاهش به مقدار 1 واحد –a ()a.dec

مثالی از عملگرهای یکانی

fun main(args: Array<String>) {
    val a = 1
    val b = true
    var c = 1

    var result: Int
    var booleanResult: Boolean

    result = -a
    println("-a = $result")

    booleanResult = !b
    println("!b = $booleanResult")

    --c
    println("--c = $c")
}

کد فوق خروجی زیر را تولید می‌کند:

-a = -1
!b = false
--c = 0

عملگرهای مقایسه و برابری

در ادامه جدولی از عملگرهای برابری و مقایسه‌ای را به همراه معنا و تابع‌های متناظرشان می‌بینید:

عملگر معنا عبارت ترجمه
> بزرگ‌تر از a > b a.compareTo(b) > 0
< کوچک‌تر از a < b a.compareTo(b) < 0
>= بزرگ‌تر مساوی a >= b a.compareTo(b) >= 0
<= کوچک‌تر مساوی a < = b a.compareTo(b) <= 0
== برابر است با a == b a?.equals(b) ?: (b === null)
!= برابر نیست با a != b !(a?.equals(b) ?: (b === null))

عملگرهای مقایسه و برابری در گردش کنترل از قبیل عبارت if، عبارت when و حلقه‌ها مورد استفاده قرار می‌گیرند.

مثالی از عملگرهای مقایسه‌ای و برابری

fun main(args: Array<String>) {

    val a = -12
    val b = 12

    // use of greater than operator
    val max = if (a > b) {
        println("a is larger than b.")
        a
    } else {
        println("b is larger than a.")
        b
    }

    println("max = $max")
}

خروجی اجرای کد فوق به صورت زیر است:

b is larger than a.
max = 12

عملگرهای منطقی

دو عملگر منطقی به صورت || و && در کاتلین وجود دارند. در ادامه جدولی از عملگرهای منطقی، معنا و تابع‌های متناظر آن‌ها را مشاهده می‌کنید:

عملگر توصیف عبارت تابع متناظر
|| در صورت true بودن هر یک از اجزا، true خواهد بود. (a>b)||(a<c) (a>b)or(a<c
&& در صورت true بودن همه عبارت‌ها مقدار true خواهد بود. (a>b)&&(a<c) (a>b)and(a<c)

توجه کنید که or و and تابع‌هایی هستند که از نمادگذاری میانوندی (infix notation) پشتیبانی می‌کنند. عملگرهای منطقی در گردش کنترل در عبارت‌هایی مانند if و when و همچنین حلقه‌ها کاربرد دارند.

مثالی از عملگرهای منطقی

fun main(args: Array<String>) {

    val a = 10
    val b = 9
    val c = -1
    val result: Boolean

    // result is true is a is largest
    result = (a>b) && (a>c) // result = (a>b) and (a>c)
    println(result)
}

خروجی اجرای کد فوق به صورت زیر است:

true

عملگر in

عملگر in برای بررسی این که یک شیء به یک مجموعه تعلق دارد یا نه، استفاده می‌شود.

عملگر عبارت ترجمه
in a in b b.contains(a)
in! a !in b b.contains(a)!

مثالی از عملگر in

fun main(args: Array<String>) {

    val numbers = intArrayOf(1, 4, 42, -3)

    if (4 in numbers) {
        println("numbers array contains 4.")
    }
}

نتیجه اجرای کد فوق به صورت زیر است:

numbers array contains 4.

عملگر دسترسی اندیس

در این بخش برخی عبارت‌ها که در عملگر دسترسی اندیس استفاده می‌شوند، به همراه تابع متناظرشان در کاتلین معرفی شده‌اند.

عبارت ترجمه
a[i] a.get(i)
a[i, n] a.get(i, n)
a[i1, i2, …, in] a.get(i1, i2, …, in)
a[i] = b a.set(i, b)
a[i, n] = b a.set(i, n, b)
a[i1, i2, …, in] = b a.set(i1, i2, …, in, b)

مثالی از عملگر دسترسی اندیس

fun main(args: Array<String>) {

    val a  = intArrayOf(1, 2, 3, 4, - 1)
    println(a[1])   
    a[1]= 12
    println(a[1])
}

با اجرای کد فوق نتیجه زیر حاصل می‌شود:

2

12

عملگر invoke

در این بخش برخی عبارت‌ها با استفاده از عملگر invoke و تابع‌های متناظر آن‌ها در کاتلین ارائه شده‌اند.

عبارت ترجمه
a() a.invoke()
a(i) a.invoke(i)
a(i1, i2, …, in) a.inkove(i1, i2, …, in)
a[i] = b a.set(i, b)
a[i, n] = b a.set(i, b)

در کاتلین پرانتزها به فراخوانی تابع عضو invoke ترجمه می‌شوند.

عملگرهای بیتی

برخلاف جاوا، در کاتلین هیچ عملگر بیتی و شیفت بیتی وجود ندارد. با این حال، برای اجرای این وظایف، تابع‌های مختلفی (با پشتیبانی از نمادگذاری میانوندی) مورد استفاده قرار می‌گیرند.

  • shl – شیفت چپ علامت‌دار
  • shr – شیفت راست علامت‌دار
  • ushr – شیفت راست بی‌علامت
  • and – و بیتی
  • or – یای بیتی
  • xor – xor بیتی
  • Inv – معکوس بیتی

همچنین در کاتلین هیچ عملگر سه‌تایی برخلاف جاوا وجود ندارد.

تبدیل نوع در کاتلین

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

int number1 = 55;
long number2 = number1;// Valid code

در مثال فوق، مقدار number1 از نوع int به صورت خودکار به نوع long تبدیل می‌شود و به متغیر number2 انتساب می‌یابد.

از سوی دیگر در کاتلین به صورت زیر عمل می‌کنیم:

val number1: Int = 55
val number2: Long = number1// Error: type mismatch.

حتی با این که اندازه Long بزرگ‌تر از Int است، کاتلین به صورت خودکار Int را به Long تبدیل نمی‌کند. بلکه باید از به صورت صریح از متد ()toLong استفاده کنیم تا نوع متغیر را به Long تبدیل کنیم. کاتلین از این کار برای جلوگیری از شگفت‌زده کردن کاربر امتناع می‌کند.

val number1: Int = 55
val number2: Long = number1.toLong()

در این بخش فهرستی از تابع‌های کاتلین را می‌بینید که برای تبدیل نوع استفاده می‌شوند:

  • toByte()
  • toShort()
  • toInt()
  • toLong()
  • toFloat()
  • toDouble()
  • toChar()

توجه کنید که تابعی برای تبدیل انواع Boolean وجود ندارد.

تبدیل از نوع بزرگ‌تر به نوع کوچک‌تر

تابع‌های مورد اشاره فوق را می‌توان در هر دو جهت یعنی تبدیل از نوع کوچک به بزرگ و برعکس، مورد استفاده قرار داد. با این حال تبدیل از نوع‌های بزرگ‌تر به انواع کوچک‌تر ممکن است باعث تعدیل یک مقدار شود. به مثال زیر توجه کنید:

fun main(args : Array<String>) {
    val number1: Int = 545344
    val number2: Byte = number1.toByte()
    println("number1 = $number1")
    println("number2 = $number2")
}

خروجی اجرای کد فوق به صورت زیر است:

number1 = 545344
number2 = 64

عبارت‌ها، گزاره‌ها و بلوک‌های کاتلین

در این بخش از مقاله آموزش کاتلین در مورد عبارت‌ها و گزاره‌های کاتلین توضیح می‌دهیم و تفاوت بین این دو را با هم و با بلوک‌های کاتلین مقایسه می‌کنیم.

عبارت‌های کاتلین

«عبارت» (Expression‌) شامل متغیر، عملگر و هر چیزی است که یک مقدار منفرد را ارزیابی می‌کند. به مثال زیر توجه کنید:

val score: Int
score = 90 + 25

در کد فوق، 90 + 25 یک عبارت است که مقدار int بازگشت می‌دهد. توجه کنید که در کاتلین if برخلاف جاوا یک عبارت است. در جاوا if یک گزاره محسوب می‌شود.

fun main(args: Array<String>) {

    val a = 12
    val b = 13
    val max: Int

    max = if (a > b) a else b
    println("$max")
}

در مثال فوق if (a > b) a else b یک عبارت است. مقدار این عبارت به متغیر max انتساب می‌یابد.

گزاره‌های کاتلین

«گزاره» (Statement) به هر چیزی گفته می‌شود که یک واحد ترکیبی اجرایی را تشکیل می‌دهد. به مثال زیر توجه کنید:

val score = 90 + 25

در کد فوق 90+25 یک عبارت است که مقدار 115 بازگشت می‌دهد، اما ;val score = 9*5 یک گزاره است. عبارت‌ها بخشی از گزاره‌ها هستند. در ادامه برخی مثال‌ها در این خصوص ارائه شده‌اند:

println("Howdy")
var a = 5
++a
max = if (a > b) a else b

بلوک‌های کاتلین

در کاتلین، بلوک به گروهی از گزاره‌ها گفته می‌شود که درون آکولاد {} قرار می‌گیرند. به مثال زیر توجه کنید:

fun main(args: Array<String>) {  // main function block
    val flag = true

    if (flag == true) {      // start of if block
        print("Hey ")
        print("jude!")
    }                        // end of if block
}                            // end of main function block

در کد فوق دو گزاره print(“Hey “) و print(” jude!”) درون بلوک if قرار دارند.

print("Hey ")
print("jude!")

به طور مشابه تابع ()main نیز یک بدنه بلوک دارد.

val flag = true

if (flag == true) {      // start of block
    print("Hey ")
    print("jude!")
}                        // end of block

کامنت‌ها در کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی روش درج توضیح یا کامنت در کاتلین می‌پردازیم و علت استفاده از آن را شرح می‌دهیم.

کامنت‌ها بخشی از برنامه هستند که به منظور درک بهتر کد چه برای خود برنامه‌نویس و چه افراد دیگری که کد را می‌خوانند مورد استفاده قرار می‌گیرند. کامنت‌ها از سوی کامپایلر کاتلین به طور کامل نادیده گرفته می‌شوند. همانند جاوا دو روش برای درج کامنت در کاتلین وجود دارد:

  • /* … */
  • // ….

روش سنتی درج کامنت

برای درج کامنت‌های چندخطی که در خطوط مختلفی نوشته می‌شوند، باید از نمادهای /* … */ استفاده کنید. کامپایلر کاتلین هر چیزی را که بین /* و */ قرار داشته باشد، نادیده می‌گیرد. به مثال زیر توجه کنید:

/* This is a multi-line comment.
 * The problem prints "Hello, World!" to the standard output.
 */
fun main(args: Array<String>) {

   println("Hello, World!")
}

از روش سنتی درج کامنت با کمی تغییر برای مستندسازی کد کاتلین (KDoc) نیز استفاده می‌شود. کامنت‌های KDoc با /** آغاز یافته و با/** خاتمه می‌یابند.

کامنت ته خط

برای درج کامنت در انتهای یک خط از برنامه باید از کاراکترهای // استفاده کنید. کامپایلر کاتلین این کاراکترهای // و هر چه پس از آن می‌ْآید را نادیده می‌گیرد. به مثال زیر توجه کنید:

// Kotlin Hello World Program
fun main(args: Array<String>) {

   println("Hello, World!")      // outputs Hello, World! on the screen
}

برنامه فوق شامل دو کامنت ته خط است:

// Kotlin Hello World Program

و

// outputs Hello, World! on the screen

استفاده از کامنت‌ها به روش صحیح

کامنت‌ها را نباید به عنوان جایگزین برای توضیح کد با نگارش ضعیف در نظر گرفت. شما باید نهایت تلاش خود را بکنید که ساختار کدتان صحیح بوده و خوانایی داشته باشد و سپس کامنت‌ها را به کد اضافه کنید.

برخی نیز بر این باورند که کد باید خود-گویا باید و می‌بایست از کامنت‌ها به ندرت استفاده کنیم. با این حال، دیدگاه عمومی با این نظر مخالف است. استفاده از کامنت در کد برای توضیح الگوریتم‌های پیچیده، regex یا تبیین سناریوهایی که از یک تکنیک به جای تکنیک‌های دیگر برای حل مسئله استفاده کنیم، هیچ اشکالی ندارد. در اغلب موارد باید از کامنت‌ها به منظور توضیح چرایی و نه چگونگی کد استفاده کنیم.

ورودی/خروجی ابتدایی کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی روش نمایش خروجی روی صفحه و همچنین دریافت ورودی‌های از کاربر در این زبان برنامه‌نویسی خواهیم پرداخت.

خروجی کاتلین

امکان استفاده از تابع‌های ()println و ()print برای ارسال خروجی به خروجی استاندارد (صفحه نمایش) وجود دارد. به مثال زیر توجه کنید:

fun main(args : Array<String>) {
    println("Kotlin is interesting.")
}

زمانی که این برنامه اجرا شود، خروجی به صورت زیر روی صفحه ظاهر می‌شود:

Kotlin is interesting.

در این کد ()println رشته درون گیومه را در خروجی ارائه می‌کند.

تفاوت بین ()println و ()print

  • تابع ()print رشته درون گیومه را پرینت می‌کند.
  • تابع ()println رشته درون گیومه را مشابه تابع ()print پرینت می‌کند. سپس کرسر به ابتدای خط بعدی می‌رود.

زمانی که از تابع ()println استفاده می‌کنیم، تابع ()System.out.println به صورت داخلی فراخوانی می‌شود. این تابع به منظور نمایش خروجی روی صفحه در جاوا استفاده می‌شود.

اگر از IntelliJ IDEA استفاده می‌کنید کرسر ماوس را در کنار عبارت println قرار داده و به منوی Navigate > Declaration قرار دهید. همچنین می‌توانید کلیدهای ترکیبی Ctrl+B (در مک کلیدهای Cmd+B) را بزنید. به این ترتیب فایل اعلان به نام Console.kt باز می‌شود. در این فایل می‌توانید ببینید که تابع ()println به صورت داخلی ()System.out.println را فراخوانی می‌کند. به طور مشابه زمانی که از تابع ()print استفاده می‌کنید، تابع ()System.out.print را در جاوا فراخوانی می‌کند.

مثال اول: ()print و ()println

fun main(args : Array<String>) {
    println("1. println ");
    println("2. println ");

    print("1. print ");
    print("2. print");
}

خروجی برنامه فوق به صورت زیر است:

1. println
2. println
1. print 2. Print

مثال دوم: پرینت متغیرها و مقادیر لفظی

fun main(args : Array<String>) {
    val score = 12.3

    println("score")
    println("$score")
    println("score = $score")
    println("${score + score}")
    println(12.3)
}

خروجی برنامه فوق نیز به صورت زیر است:

score

12.3

score = 12.3

24.6

12.3

ورودی در کاتلین

در این بخش با بحث گرفتن ورودی‌های کاربر در زبان برنامه‌نویسی کاتلین آشنا خواهیم شد. برای خواندن یک خط از رشته ورودی در کاتلین باید از تابع ()readline استفاده کنیم.

مثال سوم: پرینت رشته وارد شده از سوی کاربر

fun main(args: Array<String>) {
    print("Enter text: ")

    val stringInput = readLine()!!
    println("You entered: $stringInput")
}

خروجی برنامه فوق به صورت زیر است:

Enter text: Hmm, interesting!
You entered: Hmm, interesting!

ما می‌توانیم ورودی یک کاربر را به صورت یک رشته با استفاده از تابع ()readLine بگیریم و آن را به صورت صریح به مقادیر نوع دیگر مانند Int تبدیل کنیم.

اگر می‌خواهید ورودی‌های کاربر را با انواع دیگری از داده بگیرید، می‌توانید از شیء scanner استفاده کنید. به این منظور باید کلاس scanhner را از کتابخانه استاندارد جاوا ایمپورت کنید:

import java.util.Scanner

سپس باید شیء Scanner را از این کلاس بسازید.

val reader = Scanner(System.`in`)

اکنون شیء reader می‌تواند برای دریافت ورودی از کاربر مورد استفاده قرار گیرد.

مثال چهارم: دریافت عدد صحیح ورودی از سوی کاربر

import java.util.Scanner

fun main(args: Array<String>) {

    // Creates an instance which takes input from standard input (keyboard)
    val reader = Scanner(System.`in`)
    print("Enter a number: ")

    // nextInt() reads the next integer from the keyboard
    var integer:Int = reader.nextInt()

    println("You entered: $integer")
}

با اجرای برنامه فوق، خروجی زیر تولید می‌شود:

Enter a number: -12

You entered: -12

در این کد، شیء reader از کلاس Scanner ایجاد می‌شود. سپس متد ()nextInt فراخوانی می‌شود که یک ورودی صحیح از کاربر می‌گیرد و آن را در متغیر Integer ذخیره می‌کند.

برای دریافت ورودی‌های از نوع Long ،Float ،double و Boolean از کاربر می‌توانیم به ترتیب از متدهای ()nextLong() ،nextFloat() ،nextDouble و ()nextBoolean استفاده کنیم.

عبارت if در کاتلین

در این بخش از مقاله آموزش کاتلین به کمک برخی مثال‌ها به بررسی شیوه استفاده از عبارت if در کاتلین می‌پردازیم.

کاربرد سنتی if…else

ساختار if…else به صورت زیر است:

if (testExpression) {
   // codes to run if testExpression is true
}
else {
  // codes to run if testExpression is false
}

عبارت if در صورتی که مقدار testExpression به صورت true ارزیابی شود، بخش خاصی از کد را اجرا می‌کند. در صورتی یک بند اختیاری else وجود داشته باشد، کدهای درون بند else در صورتی اجرا می‌شوند که مقدار testExpression به صورت false ارزیابی شود.

مثالی از کاربرد سنتی if…else

fun main(args: Array<String>) {

    val number = -10

    if (number > 0) {
        print("Positive number")
    } else {
        print("Negative number")
    }
}

خروجی اجرای کد فوق به صورت زیر است:

Negative number

عبارت if در کاتلین

برخلاف جاوا و دیگر زبان‌های برنامه‌نویسی، عبارت if در کاتلین می‌تواند در یک عبارت و نه گزاره نیز مورد استفاده قرار گیرد. به مثال زیر توجه کنید:

مثالی از عبارت if در کاتلین

fun main(args: Array<String>) {

    val number = -10

    val result = if (number > 0) {
        "Positive number"
    } else {
        "Negative number"
    }

    println(result)
}

با اجرای کد فوق، خروجی زیر روی صفحه نمایش می‌یابد:

Negative number

شاخه esle کد در صورت استفاده از if به صورت یک عبارت، ضروری خواهد بود. اگر بدنه if مانند مثال زیر، تنها یک گزاره داشته باشد، استفاده از آکولادها، اختیاری خواهد بود:

fun main(args: Array<String>) {
    val number = -10
    val result = if (number > 0) "Positive number" else "Negative number"
    println(result)
}

این رفتار مشابه عملگر سه‌تایی در جاوا است. از این رو در کاتلین هیچ عملگر سه‌تایی وجود ندارد.

مثالی از بلوک if با عبارت‌های چندگانه

اگر یک شاخه از بلوک if شامل بیش از یک عبارت باشد، آخرین عبارت به عنوان مقدار بلوک بازگشت می‌یابد.

fun main(args: Array<String>) {

    val a = -9
    val b = -11

    val max = if (a > b) {
        println("$a is larger than $b.")
        println("max variable holds value of a.")
        a
    } else {
        println("$b is larger than $a.")
        println("max variable holds value of b.")
        b
    }
    println("max = $max")
}

خروجی کد فوق به صورت زیر است:

-9 is larger than -11.

max variable holds value of a.

max = -9

ساختار if..else..if در کاتلین

در کاتلین امکان بازگشت یک بلوک از کد در میان بلوک‌های متعدد به صورت زیر با استفاده از ساختار if..else…if وجود دارد.

مثالی از ساختار if..else…if

fun main(args: Array<String>) {

    val number = 0

    val result = if (number > 0)
        "positive number"
    else if (number < 0)
        "negative number"
    else 
        "zero"
    
    println("number is $result")
}

برنامه بررسی می‌کند که آیا Number یک عدد مثبت، یک عدد منفی یا صفر است.

عبارت if تودرتو در کاتلین

بک عبارت if را می‌توان درون بلوک یک عبارت if دیگر قرار دارد. این وضعیت به نام عبارت if تودرتو شناخته می‌شود.

مثالی از عبارت if تودرتو

برنامه زیر بزرگ‌ترین عدد را در میان سه عدد پیدا می‌کند.

fun main(args: Array<String>) {

    val n1 = 3
    val n2 = 5
    val n3 = -2

    val max = if (n1 > n2) {
        if (n1 > n3)
            n1
        else
            n3
    } else {
        if (n2 > n3)
            n2
        else
            n3
    }

    println("max = $max")
}

خروجی کد فوق به صورت زیر است:

max = 5

عبارت when در کاتلین

در این بخش از مقاله آموزش کاتلین با سازه when آشنا شده و برخی مثال‌های کاربردی آن را بررسی می‌کنیم.

سازه when در کاتلین

سازه when در کاتلین را می‌توان به عنوان جایگزینی برای گزاره switch در جاوا تصور کرد. این سازه بخشی از کد را در برابر جایگزین‌های متعدد ارزیابی می‌کند.

مثالی از یک عبارت ساده when

fun main(args: Array<String>) {

    val a = 12
    val b = 5

    println("Enter operator either +, -, * or /")
    val operator = readLine()

    val result = when (operator) {
        "+" -> a + b
        "-" -> a - b
        "*" -> a * b
        "/" -> a / b
        else -> "$operator operator is invalid operator."
    }

    println("result = $result")
}

خروجی کد فوق مانند زیر است:

Enter operator either +, -, * or /
*
result = 60

برنامه فوق یک رشته ورودی از کاربر می‌گیرد. فرض کنید کاربر مقدار * وارد می‌کند. در این حالت، عبارت a*b ارزیابی می‌شود و مقدار مورد نظر به یک متغیر به نام result انتساب می‌یابد.

اگر هیچ کدام از شاخه‌های شرطی برقرار نباشند، یعنی کاربر چیزی به جز +، -، * یا / وارد کرده باشد، در این صورت شاخه else ارزیابی می‌شود.

در مثال فوق باید when به عنوان یک عبارت استفاده کرده‌ایم. اما الزامی برای استفاده از when به صورت یک عبارت وجود ندارد. به مثال زیر توجه کنید:

fun main(args: Array<String>) {

    val a = 12
    val b = 5

    println("Enter operator either +, -, * or /")
    val operator = readLine()

    when (operator) {
        "+" -> println("$a + $b = ${a + b}")
        "-" -> println("$a - $b = ${a - b}")
        "*" -> println("$a * $b = ${a * b}")
        "/" -> println("$a / $b = ${a / b}")
        else -> println("$operator is invalid")
    }
}

خروجی کد فوق به صورت زیر است:

Enter operator either +, -, * or /
-
12 - 5 = 7

در این برنامه when یک عبارت نیست، چون مقدار بازگشتی آن به هیچ چیز انتساب نمی‌یابد. در این حالت، وجود شاخه else ضرورتی ندارد.

کاربردهای مختلف when در کاتلین

امکان ترکیب دو یا چند شرط با استفاده از کاما وجود دارد. به مثال زیر توجه کنید:

fun main(args: Array<String>) {

    val n = -1

    when (n) {
        1, 2, 3 -> println("n is a positive integer less than 4.")
        0 -> println("n is zero")
        -1, -2 -> println("n is a negative integer greater than 3.")
    }
}

خروجی برنامه فوق به صورت زیر است:

n is a negative integer greater than 3.

با استفاده از when امکان بررسی وجود مقداری در یک بازه فراهم می‌شود. به مثال زیر توجه کنید:

fun main(args: Array<String>) {

    val a = 100

    when (a) {
        in 1..10 -> println("A positive number less than 11.")
        in 10..100 -> println("A positive number between 10 and 100 (inclusive)")
    }
}

خروجی کد فوق به صورت زیر است:

A positive number between 10 and 100 (inclusive)

بررسی نوع یک مقدار

برای این که در زمان اجرا بررسی کنیم یک مقدار دارای نوع خاصی است یا نه، می‌توانیم از عملگرهای is و ‎!is استفاده کنیم. به مثال زیر توجه کنید:

when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

استفاده از عبارت به عنوان یک شرط شاخه. به مثال زیر توجه کنید:

fun main(args: Array<String>) {

    val a = 11
    val n = "11"

    when (n) {
        "cat" -> println("Cat? Really?")
        12.toString() -> println("Close but not close enough.")
        a.toString() -> println("Bingo! It's eleven.")
    }
}

خروجی برنامه فوق به صورت زیر است:

Bingo! It's eleven.

حلقه while و do…while در کاتلین

حلقه‌ها در برنامه‌نویسی برای تکرار یک بلوک خاص از کد استفاده می‌شوند. در این بخش از مقاله آموزش کاتلین با شیوه ایجاد حلقه while و do…while در برنامه‌نویسی کاتلین آشنا خواهیم شد. حلقه‌ها در برنامه‌نویسی برای تکرار یک بلوک خاص از کد تا زمانی که شرط معینی برقرار شود، استفاده می‌شود.

حلقه‌ها آن چیزی هستند که رایانه‌ها را به ماشین‌های جذابی تبدیل می‌کنند. تصور کنید لازم است یک جمله را 50 بار روی صفحه پرینت کنید. این کار را می‌توان با 50 بار استفاده از گزاره پرینت انجام داد. اما در صورتی که قرار باشد یک گزاره یک میلیون بار پرینت شود، این راه‌حل دیگر به کار نمی‌آید. در این صورت باید از حلقه‌ها استفاده کرد.

در ادامه این مقاله با بررسی مثال‌هایی با دو نوع حلقه while و do…while در زبان کاتلین آشنا می‌شویم. اگر با حلقه‌های while و do…while در جاوا آشنا باشید، باید بدانید که عملکرد این حلقه‌ها در کاتلین نیز دقیقاً به همان ترتیب است.

حلقه while در کاتلین

ساختار حلقه while به صورت زیر است:

while (testExpression) {
    // codes inside body of while loop
}

طرز کار حلقه while چگونه است؟

عبارت تست درون پرانتز یک عبارت بولی است. اگر عبارت تست به صورت true ارزیابی شود:

  • گزاره درون عبارت while اجرا می‌شود.
  • سپس عبارت تست دوباره ارزیابی می‌شود.

این فرایند تا زمانی که عبارت test به صورت false ارزیابی شود، تداوم می‌یابد. در این حالت حلقه while خاتمه می‌یابد.

فلوچارت حلقه While در کاتلین

آموزش کاتلین

مثالی از حلقه while در کاتلین

// Program to print line 5 times

fun main(args: Array<String>) {

    var i = 1

    while (i <= 5) {
        println("Line $i")
        ++i
    }
}

با اجرای برنامه فوق، خروجی زیر ایجاد می‌شود:

Line 1
Line 2
Line 3
Line 4
Line 5

توجه کنید که گزاره i++ درون حلقه while قرار دارد. پس از 5 بار تکرار، متغیر i به مقدار 6 افزایش می‌یابد. در این زمان عبارت تست به صورت i<=5 به صورت false ارزیابی می‌شود و حلقه خاتمه می‌یابد. اگر بدنه حلقه تنها یک گزاره داشته باشد، لزومی به استفاده از آکولاد {} وجود ندارد.

مثالی از محاسبه مجموع اعداد طبیعی

// Program to compute the sum of natural numbers from 1 to 100.
fun main(args: Array<String>) {

    var sum = 0
    var i = 100

    while (i != 0) {
        sum += i     // sum = sum + i;
        --i
    }
    println("sum = $sum")
}

خروجی برنامه فوق به صورت زیر است:

sum = 5050

در برنامه فوق متغیر sum با مقدار 0 آغاز می‌شود و i در ابتدا دارای مقدار 100 است. در هر بار تکرارِ حلقه while، متغیر sum به مقدار sum+1 انتساب می‌یابد و مقدار به اندازه 1 واحد کاهش می‌یابد تا این که i باربر با 0 باشد. برای تصور بهتر به نمونه اجرای زیر توجه کنید:

1st iteration: sum = 0+100 = 100, i = 99
2nd iteration: sum = 100+99 = 199, i = 98
3rd iteration: sum = 199+98 = 297, i = 97
... .. ...
... .. ...
99th iteration: sum = 5047+2 = 5049, i = 1
100th iteration: sum = 5049+1 = 5050, i = 0 (then loop terminates)

در بخش عملگرهای مقایسه و منطقی این مقاله در خصوص عبارت‌های تست بیشتر توضیح داده‌ایم.

حلقه do…while در کاتلین

حلقه do…while در کاتلین شبیه حلقه while است و تنها یک تفاوت با آن دارد. بدنه حلقه do…while یک بار پیش از بررسی عبارت تست، اجرا می‌شود. ساختار آن به صورت زیر است:

do {
   // codes inside body of do while loop
} while (testExpression);

طرز کار حلقه do…while چگونه است؟

کد درون بدنه سازه do یک بار و بدون بررسی عبارت تست (testExpression)‌ اجرا می‌شود. سپس عبارت تست بررسی می‌شود. اگر عبارت تست به صورت true ارزیابی شود، کدهای درون بدنه حلقه اجرا می‌شوند و عبارت تست دوباره ارزیابی می‌شود. این فرایند تا زمانی که عبارت تست به صورت false ارزیابی شود ادامه می‌یابد. زمانی که عبارت تست به صورت false ارزیابی شود، حلقه do..while خاتمه می‌یابد.

فلوچارت حلقه do..while

آموزش کاتلین

مثالی از حلقه do..while

برنامه زیر مجموع اعداد وارد شده از سوی کاربر را تا زمانی که کاربر عدد 0 را وارد کند، محاسبه می‌کند. برای دریافت ورودی کاربر از تابع ()readline استفاده شده است.

fun main(args: Array<String>) {

    var sum: Int = 0
    var input: String

    do {
        print("Enter an integer: ")
        input = readLine()!!
        sum += input.toInt()

    } while (input != "0")

    println("sum = $sum")
}

خروجی برنامه فوق به صورت زیر است:

Enter an integer: 4
Enter an integer: 3
Enter an integer: 2
Enter an integer: -6
Enter an integer: 0
sum = 3

حلقه for در کاتلین

در این بخش از مقاله آموزش کاتلین با حلقه for در این زبان برنامه‌نویسی آشنا خواهیم شد. حلقه for در کاتلین برای تکرار یک قطعه کد به تعداد مشخصی دفعه استفاده می‌شود.

برخلاف جاوا و دیگر زبان‌های رایج برنامه‌نویسی، هیچ حلقه for در کاتلین به طور سنتی وجود ندارد. بلکه در کاتلین حلقه for برای تکرار روی بازه‌ها، آرایه‌ها، map-ها و مواردی از این دست استفاده می‌شود. در واقع حلقه for روی هر چیزی که یک «تکرارکننده» (iterator) داشته باشد اجرا می‌شود. ساختار حلقه for در کاتلین به صورت زیر است:

for (item in collection) {
    // body of loop
}

مثالی از تکرار روی یک بازه

fun main(args: Array<String>) {

    for (i in 1..5) {
        println(i)
    }
}

در کد فوق، حلقه for روی یک بازه تکرار می‌کند و همه آیتم‌های منفرد را پرینت می‌گیرد. خروجی برنامه فوق به صورت زیر است:

1
2
3
4
5

اگر بدنه حلقه مانند مثال فوق، شامل تنها یک گزاره باشد، لزومی به استفاده از آکولاد {} نیست.

fun main(args: Array<String>) {
    for (i in 1..5) println(i)
}

امکان تکرار روی یک بازه با استفاده از حلقه for به این جهت فراهم آمده است که بازه‌ها یک «تکرارکننده» (iterator) ارائه می‌کنند. در خصوص تکرارکننده‌ها در ادامه این مقاله مطالب بیشتری ارائه شده است.

مثالی از روش‌های مختلف تکرار روی یک بازه

fun main(args: Array<String>) {

    print("for (i in 1..5) print(i) = ")
    for (i in 1..5) print(i)

    println()

    print("for (i in 5..1) print(i) = ")
    for (i in 5..1) print(i)             // prints nothing

    println()

    print("for (i in 5 downTo 1) print(i) = ")
    for (i in 5 downTo 1) print(i)

    println()

    print("for (i in 1..4 step 2) print(i) = ")
    for (i in 1..5 step 2) print(i)

    println()

    print("for (i in 4 downTo 1 step 2) print(i) = ")
    for (i in 5 downTo 1 step 2) print(i)
}

خروجی برنامه فوق به صورت زیر است:

for (i in 1..5) print(i) = 12345
for (i in 5..1) print(i) =
for (i in 5 downTo 1) print(i) = 54321
for (i in 1..4 step 2) print(i) = 135
for (i in 4 downTo 1 step 2) print(i) = 531

تکرار روی یک آرایه

در این بخش مثالی از تکرار روی یک آرایه String را بررسی می‌کنیم:

fun main(args: Array<String>) {

    var language = arrayOf("Ruby", "Koltin", "Python" "Java")

    for (item in language)
        println(item)
}

خروجی کد فوق به صورت زیر است:

Ruby
Koltin
Python
Java

امکان تکرار روی یک آرایه با استفاده از اندیس وجود دارد. به مثال زیر توجه کنید:

fun main(args: Array<String>) {

    var language = arrayOf("Ruby", "Koltin", "Python", "Java")

    for (item in language.indices) {

        // printing array elements having even index only
        if (item%2 == 0)
            println(language[item])
    }
}

خروجی برنامه فوق به صورت زیر است:

Ruby

Python

اگر می‌خواهید در این خصوص اطلاعات بیشتری کسب کنید به بخش «آرایه‌های کاتلین» (+) مراجعه کنید.

تکرار روی یک رشته

در این بخش مثالی از تکرار روی یک String ارائه شده است.

fun main(args: Array<String>) {

    var text= "Kotlin"

    for (letter in text) {
        println(letter)
    }
}

خروجی کد فوق به صورت زیر است:

K
o
t
l
i
n

امکان تکرار روی یک String با استفاده از اندیس و به روشی مشابه آرایه‌ها وجود دارد. به مثال زیر توجه کنید:

fun main(args: Array<String>) {

    var text= "Kotlin"

    for (item in text.indices) {
        println(text[item])
    }
}

خروجی کد فوق به صورت زیر است:

K
o
t
l
i
n

عبارت break در کاتلین

در این بخش از مقاله آموزش کاتلین، با عبارت break در این زبان برنامه‌نویسی آشنا می‌شویم. عبارت break برای خاتمه یک حلقه استفاده می‌شود. همچنین با برچسب‌های break آشنا خواهیم شد. فرض کنید مشغول کار با حلقه‌ها هستید. گاهی اوقات لازم می‌شود که حلقه را بی‌درنگ و بدون بررسی عبارت تست خاتمه ببخشیم. در این حالت، از عبارت break استفاده می‌کنیم. این عبارت موجب توقف نزدیک‌ترین حلقه می‌شود. طرز کار عبارت break در کاتلین شبیه همان عبارت در جاوا است.

طرز کار break چگونه است؟

عبارت berak تقریباً همیشه همراه با سازه if…else استفاده می‌شود. به عنوان مثال به کد زیر توجه کنید:

for (...) {
    if (testExpression) {
        break
    }
}

اگر testExpression به صورت true ارزیابی شود، break اجرا می‌شود که حلقه for را خاتمه می‌بخشد.

آموزش کاتلین

مثالی از break در کاتلین

fun main(args: Array<String>) {

    for (i in 1..10) {
        if (i == 5) {
            break
        }
        println(i)
    }
}

خروجی کد فوق به صورت زیر است:

1
2
3
4

زمانی که مقدار i برابر با 5 شود، عبارت i == 5 درون if به صورت true ارزیابی می‌شود و break اجرا خواهد شد. این ترتیب اجرای حلقه for خاتمه می‌یابد.

مثال: محاسبه مجموع تا زمانی که کاربر عدد 0 وارد کند

برنامه زیر مجموع اعداد وارد شده از سوی کاربر را تا زمانی که وی عدد 0 وارد کند، محاسبه می‌کند. برای کسب اطلاعات بیشتر در خصوص روش دریافت ورودی از کاربر به بخش «ورودی/خروجی ابتدایی» در کاتلین مراجعه کنید.

fun main(args: Array<String>) {

    var sum = 0
    var number: Int

    while (true) {
        print("Enter a number: ")
        number = readLine()!!.toInt()

        if (number == 0)
            break

        sum += number
    }

    print("sum = $sum")
}

خروجی کد فوق به صورت زیر است:

Enter a number: 4
Enter a number: 12
Enter a number: 6
Enter a number: -9
Enter a number: 0
sum = 13

در برنامه فوق، عبارت تست حلقه while همواره به صورت true ارزیابی می‌شود. در این برنامه حلقه while تا زمانی اجرا می‌شود که کاربر عدد 0 را وارد کند. زمانی که کاربر عدد 0 وارد کند، break اجرا شده و حلقه while خاتمه می‌یابد.

برچسب break در کاتلین

آن چه تا کنون در خصوص break مطرح کردیم، شکل بدون برچسب این عبارت بوده است که نزدیک‌ترین حلقه محصور را خاتمه می‌بخشد. روش دیگری نیز برای استفاده از break به صورت برچسب‌دار برای خاتمه بخشیدن به حلقه مورد نظر (و نه لزوماً نزدیک‌ترین حلقه محصور) وجود دارد.

طرز کار break برچسب‌دار چگونه است؟

آموزش کاتلین

برچسب‌ها در کاتلین با یک شناسه آغاز شده و سپس یک علامت @ می‌آید. در مثال زیر @test یک برچسب است که برای نشانه‌گذاری حلقه while بیرونی استفاده می‌شود. بدین ترتیب با استفاده از یک عبارت break به همراه برچسب به صورت break@test می‌تواند حلقه خاصی را متوقف کنید:

fun main(args: Array<String>) {

    first@ for (i in 1..4) {

        second@ for (j in 1..2) {
            println("i = $i; j = $j")

            if (i == 2)
                break@first
        }
    }
}

خروجی کد فوق به صورت زیر است:

i = 1; j = 1
i = 1; j = 2
i = 2; j = 1

در مثال فوق، عبارت i==2 به صورت true ارزیابی می‌شود و break@first اجرا می‌شود که موجب خاتمه یافتن حلقه با نشانه @first می‌شود. در مثال زیر یک نسخه کمی متفاوت از برنامه فوق را می‌بینید. در برنامه زیر break حلقه با نشانه @second را خاتمه می‌بخشد.

fun main(args: Array<String>) {

    first@ for (i in 1..4) {

        second@ for (j in 1..2) {
            println("i = $i; j = $j")

            if (i == 2)
                break@second
        }
    }
}

خروجی برنامه فوق به صورت زیر است:

i = 1; j = 1
i = 1; j = 2
i = 2; j = 1
i = 3; j = 1
i = 3; j = 2
i = 4; j = 1
i = 4; j = 2

نکته: از آنجا که break در این برنامه برای خاتمه بخشیدن به درونی‌ترین حلقه استفاده شده است، در این حالت، لزومی به استفاده از break برچسب‌دار وجود ندارد.

به طور کلی سه عبارت پرش به صورت break ،continue و return در کاتلین وجود دارند. در این بخش از مقاله آموزش کاتلین با عبارت break آشنا شدیم. عبارت‌های پرشی continue و return نیز در بخش‌های بعدی این مقاله ارائه شده‌اند.

عبارت continue در کاتلین

در این بخش از مقاله آموزش کاتلین، از عبارت continue برای پرش و رد شدن از تکرار کنونی حلقه استفاده می‌کنیم. ضمناً در این بخش از مقاله با برچسب‌های continue نیز آشنا خواهیم شد.

فرض کنید مشغول کار روی حلقه‌ها هستید. برخی اوقات لازم می‌شود که تکرار کنونی حلقه را رد کنیم. در یک چنین حالتی، از عبارت continue استفاده می‌کنیم. سازه continue موجب می‌شود که تکرار کنونی درون حلقه رد شود (اجرا نمی‌شود) و کنترل برنامه به انتهای بدنه حلقه منتقل می‌شود.

طرز کار continue چگونه است؟

عبارت continue تقریباً همیشه همراه با سازه if…else استفاده می‌شود. به مثال زیر توجه کنید:

while (testExpression1) {

    // codes1
    if (testExpression2) {
        continue
    }
    // codes2
}

اگر مقدار testExpression2 به صورت true ارزیابی شود، سازه continue اجرا می‌شود و همه کدهای درون حلقه while برای آن دفعه تکرار رد می‌شود (اجرا نخواهد شد و به گام بعدی تکرار می‌رود).

آموزش کاتلین

مثالی از continue در کاتلین

fun main(args: Array<String>) {

    for (i in 1..5) {
        println("$i Always printed.")
        if (i > 1 && i < 5) {
            continue
        }
        println("$i Not always printed.")
    }
}

خروجی کد فوق به صورت زیر است:

1 Always printed.
1 Not always printed.
2 Always printed.
3 Always printed.
4 Always printed.
5 Always printed.
5 Not always printed.

زمانی که مقدار i بزرگ‌تر از 1 و کمتر از 5 باشد، continue اجرا می‌شود که موجب اجرا نشدن گزاره زیر می‌شود:

println("$i Not always printed.")

با این حال گزاره زیر در هر تکرار حلقه اجرا می‌شود، زیرا این گزاره پیش از سازه continue قرار دارد:

println("$i Always printed.")

مثالی از محاسبه مجموع صرف اعداد مثبت

برنامه زیر مجموع حداکثر 6 عدد را که از سوی کاربر وارد می‌شوند، محاسبه می‌کند. اگر کاربر عدد منفی یا صفر وارد کند، از محاسبه صرف‌نظر می‌شود.

fun main(args: Array<String>) {

    var number: Int
    var sum = 0

    for (i in 1..6) {
        print("Enter an integer: ")
        number = readLine()!!.toInt()

        if (number <= 0)
            continue
        
        sum += number
    }
    println("sum = $sum")
}

خروجی کد فوق به صورت زیر است:

Enter an integer: 4
Enter an integer: 5
Enter an integer: -50
Enter an integer: 10
Enter an integer: 0
Enter an integer: 12
sum = 31

Continue برچسب‌دار در کاتلین

هر آنچه تا کنون در خصوص continue گفتیم به شکل بدون برچسب آن مربوط می‌شد. این نوع از سازه continue از اجرای تکرار جاری نزدیک‌ترین حلقه محصور جلوگیری می‌کند. اما می‌توان از continue برای رد شدن از تکرار هر حلقه مورد نظر (و نه لزوماً نزدیک‌ترین حلقه) استفاده کرد. بدین منظور باید از برچسب‌های continue استفاده کنیم.

طرز کار continue برچسب‌دار چگونه است؟

آموزش کاتلین

برچسب‌ها در کاتلین با یک شناسه آغاز می‌شوند و سپس کاراکتر @ می‌آید. در برنامه زیر از برچسب @outerloop استفاده می‌کنیم که حلقه while بیرونی را نشانه‌گذاری می‌کند. اینک با استفاده از continue به همراه برچسب continue@outerloop می‌توانید از اجرای کدهای یک حلقه خاص برای آن دفعه تکرار جلوگیری کنید.

مثالی از continue برچسب‌دار

fun main(args: Array<String>) {

    here@ for (i in 1..5) {
        for (j in 1..4) {
            if (i == 3 || j == 2)
                continue@here
            println("i = $i; j = $j")
        }
    }
}

خروجی کد فوق به صورت زیر است:

i = 1; j = 1
i = 2; j = 1
i = 4; j = 1
i = 5; j = 1

استفاده از continue برچسب‌دار غالباً توصیه نمی‌شود، زیرا موجب دشواری درک کد می‌شود. اگر در موقعیتی هستید که مجبور به استفاده از continue برچسب‌دار هستید، می‌توانید کد خود را با تلاش برای یافتن روش متفاوتی که خوانایی بیشتری داشته باشد، «بازسازی» (refactor) کنید.

در بخش بعدی به بررسی آخرین عبارت پرشی کاتلین یعنی return می‌پردازیم.

تابع‌های کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی تابع‌ها، ماهیت و ساختار آن‌ها و شیوه ایجاد یک تابع تعریف شده کاربر در این زبان برنامه‌نویسی می‌پردازیم.

تابع در برنامه‌نویسی به گروهی از گزاره‌های مرتبط با هم گفته می‌شود که یک وظیفه خاص را اجرا می‌کنند.

تابع‌ها برای تجزیه یک برنامه بزرگ به اجزای کوچک‌تر و ایجاد حالت ماژولار مورد استفاده قرار می‌گیرند. برای نمونه فرض کنید باید یک دایره را بر اساس ورودی از سوی کاربر ایجاد و رنگ‌آمیزی کنید. به این منظور می‌توانید دو تابع بنویسید که به ترتیب عملیات ایجاد دایره و رنگ‌آمیزی آن را اجرا می‌کنند:

  • تابع ()createCircle
  • تابع ()colorCircl

تقسیم یک برنامه بزرگ به کامپوننت‌های کوچک‌تر، موجب می‌شود که برنامه قابلیت سازمان‌دهی و مدیریت بیشتری پیدا کند.

انواع تابع‌ها

بسته به این که یک تابع از سوی کاربر تعریف شده باشد یا در کتابخانه استاندارد موجود باشد، دو نوع تابع وجود دارد:

  • تابع کتابخانه استاندارد کاتلین
  • تابع‌های تعریف شده از سوی کاربر

تابع‌های کتابخانه استاندارد کاتلین

تابع‌های کتابخانه استاندارد کاتلین، تابع‌های داخلی این زبان برنامه‌نویسی هستند که به صورت آماده ‌می‌توان از آن‌ها استفاده کرد. برای نمونه به مثال زیر توجه کنید. در این مثال تابع ()print یک تابع کتابخانه استاندارد است که در استریم خروجی استاندارد (نمایشگر) پرینت می‌کند. همچنین تابع ()sqrt جذر عدد را با مقدار از نوع Double بازگشت می‌دهد:

fun main(args: Array<String>) {

    var number = 5.5
    print("Result = ${Math.sqrt(number)}")
}

خروجی برنامه فوق به صورت زیر است:

Result = 2.345207879911715

تابع‌های تعریف ‌شده‌ کاربر

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

شیوه ایجاد تابع تعریف ‌شده‌ کاربر در کاتلین چگونه است؟

پیش از آن که بتوانیم از تابع استفاده (آن را فراخوانی) کنیم، باید آن را تعریف نماییم. روش تعریف تابع در کاتلین به صورت زیر است:

fun callMe() {
    // function body
}

برای تعریف یک تابع در کاتلین از کلیدواژه fun استفاده می‌کنیم. سپس نام تابع (شناسه) می‌آید. در مثال فوق نام تابع callMe است. در برنامه فوق، پرانتزها خالی هستند. این بدان معنی است که این تابع هیچ آرگومانی نمی‌گیرد. در مورد آرگومان‌های تابع در ادامه بیشتر توضیح خواهیم داد.

کدهای درون آکولاد، بدنه تابع را تشکیل می‌دهند.

شیوه فراخوانی تابع چگونه است؟

برای اجرای کدهای درون بدنه تابع باید آن را فراخوانی (Call) کنیم. روش انجام این کار به صورت زیر است:

callme()

گزاره فوق، تابع ()callme را که پیش‌تر اعلان کرده‌ایم، فرا می‌خواند.

آموزش کاتلین

مثالی از یک برنامه ساده با تابع

fun callMe() {
    println("Printing from callMe() function.")
    println("This is cool (still printing from inside).")
}

fun main(args: Array<String>) {
    callMe()
    println("Printing outside from callMe() function.")
}

خروجی برنامه فوق به صورت زیر است:

Printing from callMe() function.
This is cool (still printing from inside).
Printing outside from callMe() function.

تابع ()callMe در کد فوق هیچ آرگومانی نمی‌گیرد. ضمناً این تابع هیچ مقداری بازگشت نمی‌دهد. در این بخش یک مثال دیگر از تابع را بررسی می‌کنیم. تابع زیر آرگومان می‌پذیرد و مقداری نیز بازگشت می‌دهد.

مثالی از کاربرد تابع برای جمع دو عدد

fun addNumbers(n1: Double, n2: Double): Int {
    val sum = n1 + n2
    val sumInteger = sum.toInt()
    return sumInteger
}

fun main(args: Array<String>) {
    val number1 = 12.2
    val number2 = 3.4
    val result: Int

    result = addNumbers(number1, number2)
    println("result = $result")
}

خروجی برنامه فوق به صورت زیر است:

result = 15

طرز کار تابع‌های دارای آرگومان و مقدار بازگشتی در کاتلین چگونه است؟

در مثال فوق دو عدد number1 و number2 از نوع Double در طی فراخوانی تابع، به آن ارسال می‌شوند. این آرگومان‌ها به نام آرگومان‌های واقعی شناخته می‌شوند.

result = addNumbers(number1, number2)

پارامترهای n1 و n2 آرگومان‌های ارسالی را می‌گیرند. این آرگومان‌ها به نام آرگومان‌های صوری (یا پارامتر) شناخته می‌شوند.

آموزش کاتلین

آرگومان‌ها در زبان کاتلین با استفاده از کاما از هم جدا می‌شوند. همچنین نوع آرگومان صوری باید به صورت صریح نوع‌بندی شده باشد. توجه کنید که نوع داده آرگومان‌های واقعی و صوری باید با هم مطابقت داشته باشند، یعنی نوع داده آرگومان واقعی اول باید با نوع آرگومان صوری نخست یکسان باشد. به طور مشابه نوع آرگومان واقعی دوم نیز باید با نوع آرگومان صوری دوم مطابق بوده و باقی موارد نیز به همین ترتیب با هم منطبق باشند.

گزاره بازگشتی این تابع به صورت return sumInteger است. این کد موجب خاتمه یافتن تابع ()addNumbers می‌شود و بدین ترتیب کنترل برنامه به تابع ()main بازمی‌گردد. در این برنامه sumInteger از تابع ()addNumbers بازگشت می‌یابد. این مقدار به متغیر result انتساب می‌یابد.

آموزش کاتلین

توجه کنید که هر دو متغیر sumInteger و result از نوع Int هستند. همچنین نوع بازگشتی تابع در تعریف تابع تعریف می‌شود:

// return type is Int
fun addNumbers(n1: Double, n2: Double): Int {
    ... .. ...
}

اگر تابع هیچ مقداری بازگشت ندهد، نوع بازگشتی آن Unit خواهد بود. در صوتی که نوع بازگشتی به صورت Unit باشد، تعیین نوع بازگشتی در تعریف تابع اختیاری خواهد بود.

مثالی از نمایش نام با استفاده از تابع

fun main(args: Array<String>) {
    println(getName("John", "Doe"))
}

fun getName(firstName: String, lastName: String): String = "$firstName $lastName"

خروجی کد فوق به صورت زیر است:

John Doe

در کد فوق، تابع ()getName دو آرگومان String می‌گیرد و یک مقدار از نوع String بازگشت می‌دهد. در صورتی که تابع همانند مثال فوق، یک عبارت منفرد را بازگشت می‌دهد، الزامی برای استفاده از آکولاد {} برای بدنه تابع پس از علامت = وجود ندارد.

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

fun getName(firstName: String, lastName: String): String = "$firstName $lastName"

با شیوه زیر جایگزین کرد:

fun getName(firstName: String, lastName: String) = "$firstName $lastName"

در این بخش از مقاله آموزش کاتلین توضیح مختصری در خصوص تابع‌های کاتلین و ماهیت و طرز کار آن‌ها ارائه کردیم. در بخش‌های بعدی در این خصوص بیشتر صحبت خواهیم کرد.

فراخوانی تابع به شیوه میانوندی

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

زمانی که از عملگرهای || و && استفاده می‌کنیم، کامپایلر به ترتیب به دنبال تابع‌های or و and می‌گردد و آن‌ها را در پس‌زمینه فراخوانی می‌کند. این دو تابع از نمادگذاری میانوندی پشتیبانی می‌کنند.

مثالی از تابع or و and در کاتلین

fun main(args: Array<String>) {
    val a = true
    val b = false
    var result: Boolean

    result = a or b // a.or(b)
    println("result = $result")

    result = a and b // a.and(b)
    println("result = $result")
}

خروجی برنامه فوق به صورت زیر است:

result = true

result = false

در برنامه فوق، از عبارت a or b به جای a.or(b) و از عبارت a and b به جای a.and(b) استفاده شده است. دلیل این که امکان چنین کاری وجود دارد، این است که این دو تابع از نمادگذاری میانوندی پشتیبانی می‌کنند.

شیوه ایجاد تابعی با نمادگذاری میانوندی در کاتلین چگونه است؟

در صورتی که تابعی همه شرایط زیر را داشته باشد، می‌توانیم آن را با استفاده از نمادگذاری میانوندی فراخوانی کنیم:

  • ابن تابع یک تابع عضو (یا تابع بسط) باشد.
  • این تابع تنها یک پارامتر داشته باشد.
  • این تابع با کلیدواژه infix نشانه‌گذاری شده باشد.

مثالی از تابع تعریف ‌شده‌ کاربر با نمادگذاری میانوندی

class Structure() {

    infix fun createPyramid(rows: Int) {
        var k = 0
        for (i in 1..rows) {
            k = 0
            for (space in 1..rows-i) {
                print("  ")
            }
            while (k != 2*i-1) {
                print("* ")
                ++k
            }
            println()
        }
    }
}

fun main(args: Array<String>) {
    val p = Structure()
    p createPyramid 4       // p.createPyramid(4)
}

خروجی برنامه فوق به صورت زیر است:

      *
    * * *
  * * * * *
* * * * * * *

در برنامه فوق، ()createPyramid یک تابع میانوندی است که یک ساختار هرمی ایجاد می‌کند. این تابع یک تابع عضو کلاس Structure است که تنها یک پارامتر از نوع Int می‌گیرد و با کلیدواژه infix آغاز می‌شود.

تعداد ردیف‌های هرم به آرگومان‌های ارسالی به تابع بستگی دارد.

آرگومان‌های پیش‌فرض و نام‌دار در کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی آرگومان‌های پیش‌فرض و نام‌دار در این زبان برنامه‌نویسی می‌پردازیم و مثال‌هایی را در این خصوص بررسی خواهیم کرد.

آرگومان پیش‌فرض در کاتلین

در زبان کاتلین، می‌توان در تعریف تابع، مقادیر پیش‌فرضی برای پارامترها ارائه کرد. اگر تابع با آرگومان‌های ارسالی فراخوانی شود، این آرگومان‌ها به عنوان پارامتر استفاده می‌شوند. با این حال، اگر تابع بدون ارسال پارامتر (ها) فراخوانی شود، آرگومان‌های پیش‌فرض مورد استفاده قرار می‌گیرند.

طرز کار آرگومان پیش‌فرض در کاتلین چگونه است؟

در این بخش با طرز کار آرگومان‌های پیش‌فرض در حالت‌های مختلف آشنا می‌شویم.

حالت اول: همه آرگومان‌ها ارسال شوند

آموزش کاتلین

تابع ()foo دو آرگومان می‌گیرد. این آرگومان‌ها دارای مقادیر پیش‌فرض هستند. با این حال در برنامه فوق، تابع ()foo با ارسال هر دو آرگومان فراخوانی شده است. از این رو آرگومان‌های پیش‌فرض مورد استفاده قرار نگرفته‌اند. درون تابع ()foo مقادیر letter و number به ترتیب برابر با ‘x’ و 2 هستند.

حالت دوم: بعضی آرگومان‌ها به تابع ارسال نشوند

آموزش کاتلین

در این حالت فرض می‌کنیم تنها آرگومان اول به تابع ()foo ارسال شده باشد. در این صورت آرگومان اول از مقدار ارسالی به تابع استفاده می‌کند. با این حال آرگومان دوم، یعنی number مقدار پیش‌فرض را می‌گیرد، زیرا آرگومان دوم در طی فراخوانی تابع ارسال نشده است. درون تابع ()foo مقادیر letter و number به ترتیب برابر با ‘y’ و 15 هستند.

حالت سوم: هیچ آرگومانی ارسال نشود

آموزش کاتلین

در این حالت تابع ()foo بدون ارسال هیچ آرگومانی فراخوانی می‌شود. از این رو در هر دو مورد از مقادیر پیش‌فرض استفاده می‌شود. بدین ترتیب درون تابع ()foo مقادیر letter و number به ترتیب برابر با ‘a’ و 15 هستند.

مثالی از آرگومان پیش‌فرض در کاتلین

fun displayBorder(character: Char = '=', length: Int = 15) {
    for (i in 1..length) {
        print(character)
    }
}

fun main(args: Array<String>) {
    println("Output when no argument is passed:")
    displayBorder()

    println("\n\n'*' is used as a first argument.")
    println("Output when first argument is passed:")
    displayBorder('*')

    println("\n\n'*' is used as a first argument.")
    println("5 is used as a second argument.")
    println("Output when both arguments are passed:")
    displayBorder('*', 5)

}

خروجی برنامه فوق به صورت زیر است:

Output when no argument is passed:
===============

'*' is used as a first argument.
Output when first argument is passed:
***************
'*' is used as a first argument.
5 is used as a second argument.
Output when both arguments are passed:
*****

آرگومان نام‌دار در کاتلین

پیش از آن که در مورد آرگومان نام‌دار در کاتلین صحبت کنیم، یک تغییر کوچک را در کد فوق مورد بررسی قرار می‌دهیم.

fun displayBorder(character: Char = '=', length: Int = 15) {
    for (i in 1..length) {
        print(character)
    }
}

fun main(args: Array<String>) {
    displayBorder(5)
}

در برنامه فوق تلاش می‌کنیم تا آرگومان دوم را به تابع ()displayBorder ارسال کنیم و از آرگومان پیش‌فرض برای آرگومان اول استفاده می‌کنیم. با این حال، این کد تولید خطا خواهد کرد. دلیل این امر آن است که کامپایلر فکر می‌کند که ما تلاش می‌کنیم مقدار 5 (با نوع Int) را به یک کاراکتر (از نوع Char) ارسال کنیم. برای حل این مشکل باید از آرگومان‌های نام‌دار استفاده کنیم:

مثالی از آرگومان نام‌دار در کاتلین

fun displayBorder(character: Char = '=', length: Int = 15) {
    for (i in 1..length) {
        print(character)
    }
}

fun main(args: Array<String>) {
    displayBorder(length = 5)
}

خروجی برنامه فوق به صورت زیر است:

=====

در برنامه فوق، از یک آرگومان نام‌دار به صورت length = 5 برای مشخص ساختن این نکته استفاده کرده‌ایم که پارامتر length در تعریف تابع باید این مقدار را بگیرد. توجه کنید که در این حالت، ترتیب آرگومان اهمیتی ندارد.

آموزش کاتلین

بدین ترتیب آرگومان نخست به نام character از مقدار پیش‌فرض ‘=’ در برنامه استفاده می‌کند.

تابع‌های بازگشتی در کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی روش ایجاد تابع‌های بازگشتی در این زبان برنامه‌نویسی می‌پردازیم. «تابع بازگشتی» (Recursive Function) به تابعی گفته می‌شود که خودش را فراخوانی کند. همچنین با مفهوم تابع «بازگشتی انتهایی» (Tail Recursion)‌ در کاتلین آشنا خواهیم شد.

چنان که اشاره کردیم تابعی که خودش را فراخوانی کند، تابع بازگشتی نامیده می‌شود و این تکنیک به طور کلی به نام «بازگشت» (recursion) خوانده می‌شود. یک مثال فیزیکی از این پدیده را می‌توان در زمان قرار دادن دو آینه در روبروی هم مشاهده کرد. هر شیئی که در میان این دو آینه قرار داشته باشد به صورت بازگشتی در آن‌ها بازتاب می‌یابد.

طرز کار بازگشت در برنامه‌نویسی چگونه است؟

fun main(args: Array<String>) {
    ... .. ...
    recurse()
    ... .. ...
}

fun recurse() {
    ... .. ...
    recurse()
    ... .. ...
}

در کد فوق تابع ()recurse از داخل خود بدنه تابع ()recurse فراخوانی می‌شود. طرز کار آن به صورت زیر است:

آموزش کاتلین

در تصویر فوق می‌بینیم که فراخوانی بازگشتی تا ابد ادامه می‌یابد و موجب بروز بازگشت نامتناهی می‌شود. برای جلوگیری از بازگشت نامتناهی از گزاره if…else (یا رویکردهای مشابه) استفاده می‌شود که در یک شاخه از آن فراخوانی بازگشتی انجام می‌یابد و در شاخه دیگر (حصول شرایط مطلوب) ‌متوقف می‌شود.

مثالی از یافتن فاکتوریل عدد با استفاده از بازگشت در کاتلین

fun main(args: Array<String>) {
    val number = 4
    val result: Long

    result = factorial(number)
    println("Factorial of $number = $result")
}

fun factorial(n: Int): Long {
    return if (n == 1) n.toLong() else n*factorial(n-1)
}

خروجی برنامه فوق به صورت زیر است:

Factorial of 4 = 24

طرز کار برنامه فوق چگونه است؟

فراخوانی بازگشتی تابع ()factorial را می‌توان با استفاده از تصویر زیر توضیح داد:

آموزش کاتلین

مراحل اجرای برنامه فوق به صورت زیر است:

factorial(4)// 1st function call. Argument: 4
4*factorial(3)// 2nd function call. Argument: 3
4*(3*factorial(2))// 3rd function call. Argument: 2
4*(3*(2*factorial(1)))// 4th function call. Argument: 1
4*(3*(2*1))
24

بازگشت انتهایی در کاتلین

«بازگشت انتهایی» (Tail Recursion) یک مفهوم عمومی است و یک ویژگی زبان کاتلین محسوب نمی‌شود. برخی زبان‌های برنامه‌نویسی شامل کاتلین از آن برای بهینه‌سازی فراخوانی‌های بازگشتی استفاده می‌کنند، در حالی که برخی زبان‌های دیگر (مانند پایتون) از آن‌ها پشتیبانی نمی‌کنند.

بازگشت انتهایی در کاتلین چیست؟

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

اما در فراخوانی بازگشتی انتهایی، محاسبات ابتدا اجرا می‌شوند و سپس فراخوانی‌های بازگشتی اجرا می‌شوند. به این ترتیب فراخوانی بازگشتی، نتیجه اجرای گام قبلی را به فراخوانی بازگشتی بعدی ارائه می‌کند. این امر موجب می‌شود که فراخوانی بازگشتی معادل اجرای حلقه باشد و از خطر بروز «سرریز پشته» (stack overflow) جلوگیری می‌کند.

شرط بازگشت انتهایی

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

  • مثال اول

تابع زیر برای اجرا به صورت «بازگشت انتهایی» مناسب نیست، زیرا فراخوانی تابع به خودش یعنی خط (n*factorial(n-1، آخرین عملیات آن محسوب نمی‌شود.

fun factorial(n: Int): Long {

    if (n == 1) {
        return n.toLong()
    } else {
        return n*factorial(n - 1)     
    }
}
  • مثال دوم

تابع زیر برای اجرا به صورت بازگشت انتهایی مناسب است، زیرا فراخوانی تابع به خودش یعنی fibonacci(n-1, a+b, a) آخرین عملیات آن است.

fun fibonacci(n: Int, a: Long, b: Long): Long {
    return if (n == 0) b else fibonacci(n-1, a+b, a)
}

برای این که به کامپایلر اعلام کنیم که یک تابع بازگشتی را در کاتلین به صورت بازگشت انتهایی اجرا کند، باید تابع را با مادیفایر tailrec مشخص سازیم.

مثالی از بازگشت انتهایی

import java.math.BigInteger

fun main(args: Array<String>) {
    val n = 100
    val first = BigInteger("0")
    val second = BigInteger("1")

    println(fibonacci(n, first, second))
}

tailrec fun fibonacci(n: Int, a: BigInteger, b: BigInteger): BigInteger {
    return if (n == 0) a else fibonacci(n-1, b, a+b)
}

خروجی کد فوق به صورت زیر است:

354224848179261915075

این برنامه صدمین جمله سری فیبوناچی را محاسبه می‌کند. از آنجا که خروجی ممکن است عدد صحیح بسیار بزرگی باشد، باید از کلاس BigInteger در کتابخانه استاندارد جاوا کمک بگیریم. در مثال فوق، تابع ()fibonacci با مادیفایری به نام tailrec مشخص شده است و از این رو امکان اجرای بازگشت انتهایی را دارد. به این ترتیب کامپایلر فرایند بازگشتی را در این حالت بهینه‌سازی می‌کند.

اگر تلاش کنید جمله 20،000 (یا هر عدد صحیح بزرگ دیگر) سری فیبوناچی را بدون بازگشت انتهایی محاسبه کنید، کامپایلر یک استثنا به صورت java.lang.StackOverflowError ایجاد می‌کند. با این حال، برنامه ما بدون هیچ مشکلی کار می‌کند. دلیل این امر آن است که ما از بازگشت انتهایی استفاده کرده‌ایم که از یک حلقه بهینه بر مبنای نسخه‌ای از بازگشت سنتی استفاده می‌کند.

مثالی از فاکتوریل با استفاده از بازگشت انتهایی

مثال فوق برای محاسبه فاکتوریل یک عدد (مثال اول) را نمی‌توان برای اجرا به صورت بازگشت انتهایی بهینه‌سازی کرد. اما برنامه زیر همین وظیفه را به روش متفاوتی اجرا می‌کند.

fun main(args: Array<String>) {
    val number = 5
    println("Factorial of $number = ${factorial(number)}")
}

tailrec fun factorial(n: Int, run: Int = 1): Long {
    return if (n == 1) run.toLong() else factorial(n-1, run*n)
}

خروجی برنامه فوق به صورت زیر است:

Factorial of 5 = 120

کامپایلر می‌تواند در این برنامه فرایند بازگشتی را به صورت یک تابع بازگشتی که امکان اجرای بازگشت انتهایی دارد، بهینه‌سازی کند و ما از مادیفایر tailrec استفاده کرده‌ایم که موجب بهینه‌سازی بازگشت از سوی کامپایلر می‌شود.

کلاس و شیء در کاتلین

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

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

برنامه‌نویسی شی‌ءگرا

در سبک برنامه‌نویسی شیء‌گرا (OOP) یک مسئله پیچیده با ایجاد شیء‌هایی به مجموعه‌های کوچک‌تر تقسیم می‌شود. این اشیا دو خصوصیت دارند:

  • حالت
  • رفتار

در ادامه برخی مثال‌هایی از اشیا را بررسی می‌کنیم.

  1. لامپ (Lamp) یک شیء است:
    1. این شیء می‌تواند حالت‌های روشن (On) و خاموش (Off) داشته باشد.
    2. این شیء می‌تواند رفتار روشن کردن (turn on) و خاموش کردن (turn off) را بگیرد.
  2. دوچرخه (Bicycle) یک شیء است.
    1. این شیء می‌تواند حالت‌های «دنده کنونی» (current gear)، «دو چرخ» (two wheels)‌، «تعداد دنده» (number of gear) ‌و بسیاری حالت‌های دیگر را داشته باشد.
    2. این شیء می‌تواند رفتار «ترمز کردن» (braking)، «پدال زدن» (changing gears)، «تغییر دادن دنده» (changing gears) و بسیاری رفتارهای دیگر را داشته باشد.

در بخش‌های بعدی این مقاله آموزش کاتلین در خصوص ویژگی‌های مختلف برنامه‌نویسی شیءگرا از قبیل کپسوله‌سازی داده‌ها، وراثت، چندریختی و غیره صحبت خواهیم کرد. در این بخش صرفاً روی مبانی مقدماتی شیءگرایی در کاتلین تمرکز می‌کنیم.

کلاس کاتلین

پیش از آن که بتوان اشیایی در کاتلین ساخت، باید یک کلاس در این زبان تعریف کرد. کلاس، یک نقشه اولیه برای ساخت شیء محسوب می‌شود. کلاس را می‌توان مانند یک رسم اولیه (پروتوتایپ) از یک خانه تصور کرد. این کلاس شامل جزییاتی در مورد طبقات، درها، پنجره‌های و اجزای دیگر خانه دارد. بر اساس این توصیف‌ها می‌توان خانه را ساخت. به این ترتیب خانه ساخته شده یک شیء خواهد بود. همان طور که خانه‌های زیادی را می‌توان از روی یک نقشه اولیه واحد ساخت، از روی یک کلاس نیز می‌توان اشیای زیادی ایجاد کرد.

شیوه تعریف کلاس در کاتلین چگونه است؟

برای تعریف یک کلاس در کاتلین باید از کلیدواژه class استفاده کنیم:

class ClassName {
    // property
    // member function
    ... .. ...
}

به مثال زیر توجه کنید:

class Lamp {

    // property (data member)
    private var isOn: Boolean = false

    // member function
    fun turnOn() {
        isOn = true
    }

    // member function
    fun turnOff() {
        isOn = false
    }
}

در مثال فوق یک کلاس با نام Lamp تعریف کرده‌ایم. این کلاس دارای یک مشخصه به نام isOn است که همانند یک متغیر تعریف شده است. همچنین دو تابع عضو به نام‌های ()turnOn و ()turnOff دارد.

یک مشخصه در کلاس‌های کاتلین یا باید مقداردهی شود و یا به صورت «مجرد» (abstract) اعلان شود. در مثال فوق، مشخصه isOn به صورت False مقداردهی شده است. کلاس‌ها، اشیا، مشخصه‌ها، تابع عضو و غیره، همگی دارای مادیفایرهای «نمایانی» (Visibility) هستند. برای نمونه مشخصه isOn خصوصی است. این بدان معنی است که مشخصه isOn تنها از درون کلاس Lamp می‌تواند تغییر یابد.

مادیفایرهای دیگر نمایانی در کاتلین به شرح زیر هستند:

  • Private – صرفاً از درون خود کلاس قابل مشاهده (دسترسی) است.
  • Public – در هر کجا نمایان است.
  • Protected – برای کلاس و زیرکلاس‌های آن نمایان است.
  • Internal – هر کلاینت درون ماژول می‌تواند به آن دسترسی داشته باشد.

در بخش‌های بعدی در مورد مادیفایرهای protected و internal بیشتر صحبت خواهیم کرد. در برنامه فوق، تابع‌های عضو ()turnOn و ()turnOff به صورت عمومی (public) هستند، در حالی که مشخصه isOn خصوصی (private) است.

اشیای کاتلین

در زمان تعریف یک کلاس، صرفاً مشخصه‌های شیء تعریف می‌شوند و هیچ حافظه یا فضای ذخیره‌سازی به آن تخصیص نمی‌یابد. برای دسترسی به تابع‌های عوض درون کلاس باید از روی آن اشیایی ایجاد کرد. در ادامه مثالی از ایجاد یک شیء بر مبنای کلاس Lamp می‌بینید که در بخش قبل تعریف کردیم:

class Lamp {

    // property (data member)
    private var isOn: Boolean = false

    // member function
    fun turnOn() {
        isOn = true
    }

    // member function
    fun turnOff() {
        isOn = false
    }
}

fun main(args: Array<String>) {

    val l1 = Lamp() // create l1 object of Lamp class
    val l2 = Lamp() // create l2 object of Lamp class
}

برنامه فوق دو شیء به نام‌های l1 و l2 از روی کلاس Lamp ایجاد می‌کند. مشخصه isOn برای هر دو لامپ l1 و l2 به صورت false است.

شیوه دسترسی به اعضا چگونه است؟

امکان دسترسی به مشخصه‌ها و تابع‌های عضو یک کلاس با استفاده از نماد نقطه (.) وجود دارد. به مثال زیر توجه کنید:

l1.turnOn()

گزاره فوق تابع ()turnOn را روی شیء l1 فرا می‌خواند. به مثال زیر نیز توجه کنید:

l2.isOn = true

در اینجا مقدار مشخصه isOn مربوط به شیء l2 را به صورت true تنظیم می‌کنیم. توجه کنید که مشخصه isOn به صورت خصوصی تعریف شده است و اگر تلاش کنید از خارج از کلاس به آن دسترسی داشته باشید، یک استثنا ایجاد می‌شود.

مثالی از کلاس و شیء کاتلین

class Lamp {

    // property (data member)
    private var isOn: Boolean = false

    // member function
    fun turnOn() {
        isOn = true
    }

    // member function
    fun turnOff() {
        isOn = false
    }

    fun displayLightStatus(lamp: String) {
        if (isOn == true)
            println("$lamp lamp is on.")
        else
            println("$lamp lamp is off.")
    }
}

fun main(args: Array<String>) {

    val l1 = Lamp() // create l1 object of Lamp class
    val l2 = Lamp() // create l2 object of Lamp class

    l1.turnOn()
    l2.turnOff()

    l1.displayLightStatus("l1")
    l2.displayLightStatus("l2")
}

خروجی برنامه فوق به صورت زیر است:

l1 Lamp is on.
l2 Lamp is off.

در این بخش در خصوص برنامه فوق برخی توضیح‌ها را ارائه می‌کنیم:

  • ابتدا کلاس Lamp ایجاد می‌شود.
  • این کلاس دارای مشخصه isOn و سه تابع عضو به صورت ()turnOn() ،turnOff و ()displayLightStatus است.
  • دو شیء l1 و l2 مربوط به کلاس Lamp در تابع ()main ایجاد شده‌اند.
  • در ادامه تابع ()turnOn با استفاده از شیء l1 به صورت ()l1.turnOn فراخوانی می‌شود. این متد موجب می‌شود که متغیر وهله‌ای isOn مربوط به شیء l1 به صورت true تنظیم شود.
  • سپس تابع ()turnOff با استفاده از شیء l2 به صورت ()l2.turnOff فراخوانی می‌شود. این متد نیز موجب می‌شود که متد وهله‌ای isOff مربوط به شیء l2 به صورت false تنظیم شود.
  • در نهایت تابع ()displayLightStatus برای هر دو شیء l1 و l2 فراخوانی می‌شود و پیام مناسبی بسته به این که مشخصه isOn مقدار true یا false داشته باشد، تنظیم می‌کند.

توجه کنید که مشخصه isOn درون کلاس به صورت false مقداردهی می‌شود. زمانی که یک شیء از کلاس ایجاد می‌شود، مشخصه isOn مربوط به آن شیء به صورت خودکار روی false تنظیم می‌شود. بنابراین لزومی به فراخوانی ()turnOff برای تنظیم مقدار مشخصه isOn به مقدار false وجود نخواهد داشت.

به مثال زیر توجه کنید:

class Lamp {

    // property (data member)
    private var isOn: Boolean = false

    // member function
    fun turnOn() {
        isOn = true
    }

    // member function
    fun turnOff() {
        isOn = false
    }

    fun displayLightStatus() {
        if (isOn == true)
            println("lamp is on.")
        else
            println("lamp is off.")
    }
}

fun main(args: Array<String>) {

    val lamp = Lamp()
    lamp.displayLightStatus()
}

خروجی برنامه فوق چنین است:

lamp is off.

بدین ترتیب به انتهای این بخش از مقاله آموزش کاتلین می‌رسیم. در این بخش با مفاهیم مقدماتی برنامه‌نویسی شیء‌گرا در کاتلین آشنا شدیم.

سازنده در کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی «سازنده‌ها» (constructors) در کاتلین می‌پردازیم. در این مسیر هر دو نوع سازنده اولیه و ثانویه را معرفی کرده و همچنین بلوک‌های مقداردهی را به کمک مثال‌هایی مورد بررسی قرار می‌دهیم.

سازنده یا constructor یک روش منسجم برای مقداردهی مشخصه‌های کلاس محسوب می‌شود. سازنده یک تابع عضو خاص کلاس است که در زمان مقداردهی (ایجاد) شیء فراخوانی می‌شود. با این حال، طرز کار سازنده‌ها در کاتلین نسبت به جاوا و زبان‌های دیگر برنامه‌نویسی کمی متفاوت است.

در کاتلین دو نوع سازنده به شرح زیر داریم:

  • سازنده اولیه: روش منسجمی برای مقداردهی یک کلاس است.
  • سازنده ثانویه: امکان تعریف منطق اضافی برای مقداردهی را فراهم می‌سازد.

سازنده اولیه

سازنده اولیه بخشی از هدر کلاس محسوب می‌شود. به مثال زیر توجه کنید:

class Person(val firstName: String, var age: Int) {
    // class body
}

بلوک کدی که درون پرانتز احاطه شده، سازنده اولیه است. ساختار کلی آن به صورت زیر است:

(val firstName: String, var age: Int)

سازنده دو مشخصه اعلان می‌کند که یکی firstName است که یک مشخصه فقط-خواندنی است، زیرا با کلیدواژه val اعلان شده است و دیگری مشخصه age است که امکان خواندن-نوشتن دارد، زیرا با کلیدواژه var اعلان شده است.

مثالی از سازنده اولیه

fun main(args: Array<String>) {

    val person1 = Person("Joe", 25)

    println("First Name = ${person1.firstName}")
    println("Age = ${person1.age}")
}

class Person(val firstName: String, var age: Int) {

}

خروجی برنامه فوق چنین است:

First Name = Joe
Age = 25

زمانی که شیء کلاس Person ایجاد شود، مقادیر “Joe” و 25 طوری به آن ارسال می‌شوند که گویی Person یک تابع است. این امر موجب مقداردهی مشخصه‌های firstName و age به ترتیب با مقادیر “Joe” و 25 می‌شود. روش‌های دیگری نیز برای استفاده از سازنده‌های اولیه وجود دارد که در بخش بعدی توضیح می‌دهیم.

سازنده اولیه و بلوک‌های مقداردهی

سازنده اولیه دارای یک ساختار مقید است و نمی‌تواند شامل هیچ کدی باشد. برای نوشتن کد مقداردهی اولیه (و نه فقط کدی که مشخصه‌ها را مقداردهی کند) باید از یک بلوک مقداردهی استفاده کنیم. کلیدواژه ابتدایی این بلوک به صورت init است. در ادامه یک بلوک مقداردهی اولیه به ابتدای مثال قبلی اضافه می‌کنیم:

fun main(args: Array<String>) {
    val person1 = Person("joe", 25)
}

class Person(fName: String, personAge: Int) {
    val firstName: String
    var age: Int

    // initializer block
    init {
        firstName = fName.capitalize()
        age = personAge

        println("First Name = $firstName")
        println("Age = $age")
    }
}

خروجی برنامه فوق به صورت زیر است:

First Name = Joe

Age = 25

در مثال فوق، پارامترهای fName و personage درون پرانتزها، مقادیر “Joe” و 25 به ترتیب در زمان ایجاد شیء person1 دریافت می‌شود. با این حال fName و personage بدون استفاده از var و val استفاده شده‌اند و مشخصه‌های کلاس Person هستند.

کلاس Person دو مشخصه firstName و age را اعلان کرده است. زمانی که شیء person1 ایجاد می‌شود، کد درون بلوک مقداردهی اجرا می‌شود. بلوک مقداردهی نه تنها مشخصه‌ها را مقداردهی اولیه می‌کند بلکه آن‌ها را پرینت نیز می‌کند.

برای اجرای این وظیفه یک روش دیگر وجود دارد:

fun main(args: Array<String>) {
    val person1 = Person("joe", 25)
}

class Person(fName: String, personAge: Int) {
    val firstName = fName.capitalize()
    var age = personAge

    // initializer block
    init {
        println("First Name = $firstName")
        println("Age = $age")
    }
}

برای ایجاد تمایز بین پارامتر سازنده و مشخصه باید از نام‌ها متفاوتی استفاده کنیم. برای مثال ما از نام‌های fName و firstName و همچنین personAge و age بهره می‌گیریم. به طور معمول از نام‌هایی مانند ‎_firstName و ‎_age به جای نام‌های کاملاً متفاوت برای پارامترهای سازنده استفاده می‌کنیم. به مثال زیر توجه کنید:

class Person(_firstName: String, _age: Int) {
    val firstName = _firstName.capitalize()
    var age = _age

    // initializer block
    init {
        ... .. ...
    }
}

مقدار پیش‌فرض در سازنده اولیه

برای پارامترهای سازنده می‌توان مقادیر پیش‌فرض ارائه کرد. به مثال زیر توجه کنید:

fun main(args: Array<String>) {

    println("person1 is instantiated")
    val person1 = Person("joe", 25)

    println("person2 is instantiated")
    val person2 = Person("Jack")

    println("person3 is instantiated")
    val person3 = Person()
}

class Person(_firstName: String = "UNKNOWN", _age: Int = 0) {
    val firstName = _firstName.capitalize()
    var age = _age

    // initializer block
    init {
        println("First Name = $firstName")
        println("Age = $age\n")
    }
}

خروجی برنامه فوق به صورت زیر است:

First Name = Joe
Age = 25

person2 is instantiated
First Name = Jack
Age = 0

person3 is instantiated
First Name = UNKNOWN
Age = 0

سازنده ثانویه کاتلین

کلاس در کاتلین می‌تواند شامل یک یا چند سازنده ثانویه نیز باشد. این سازنده‌های ثانویه با استفاده از کلیدواژه constructor ساخته می‌شوند.

سازنده‌های ثانویه در کاتلین رایج نیستند. رایج‌ترین کاربرد سازنده ثانویه در زمانی است که لازم است یک کلاس را بسط دهیم که چند سازنده دارد و کلاس را به روش‌های متفاوتی بسط می‌دهد. این موضوع با بحث وراثت در کاتلین (+) مرتبط است که در ادامه بیشتر بررسی خواهیم کرد. روش ساخت سازنده ثانویه در کاتلین به شرح زیر است:

class Log {
    constructor(data: String) {
        // some code
    }
    constructor(data: String, numberOfData: Int) {
        // some code
    }
}

کلاس log و سازنده ثانویه دارد، اما هیچ سازنده اولیه ندارد. این کلاس را می‌توان به صورت زیر بسط داد:

class Log {
    constructor(data: String) {
        // code
    }
    constructor(data: String, numberOfData: Int) {
        // code
    }
}

class AuthLog: Log {
    constructor(data: String): super(data) {
        // code
    }
    constructor(data: String, numberOfData: Int): super(data, numberOfData) {
        // code
    }
}

در این مثال، سازنده‌های کلاس مشتق‌‌شده به نام AuthLog، سازنده متناظر کلاس مبنا به نام Log را فراخوانی می‌کنند. به این منظور از ()super استفاده می‌کنیم.

Kotlin

در کاتلین می‌توان یک سازنده را از سازنده دیگر همان کلاس نیز با استفاده از ()this فراخوانی کرد.

class AuthLog: Log {
    constructor(data: String): this(data, 10) {
        // code
    }
    constructor(data: String, numberOfData: Int): super(data, numberOfData) {
        // code
    }
}

Kotlin

مثالی از سازنده ثانویه در کاتلین

fun main(args: Array<String>) {

    val p1 = AuthLog("Bad Password")
}

open class Log {
    var data: String = ""
    var numberOfData = 0
    constructor(_data: String) {

    }
    constructor(_data: String, _numberOfData: Int) {
        data = _data
        numberOfData = _numberOfData
        println("$data: $numberOfData times")
    }
}

class AuthLog: Log {
    constructor(_data: String): this("From AuthLog -> " + _data, 10) {
    }

    constructor(_data: String, _numberOfData: Int): super(_data, _numberOfData) {
    }
}

خروجی برنامه فوق به صورت زیر است:

From AuthLog -> Bad Password: 10 times

نکته: سازنده ثانویه باید کلاس مبنا را مقداردهی کند یا در صورتی که مانند مثال فوق، کلاس مورد نظر سازنده اولیه نداشته باشد، سازنده دیگری را «نمایندگی» (delegate) کند.

Getter-ها و Setter-ها در کاتلین

در این بخش از مقاله آموزش کاتلین با شیوه استفاده از Getter-ها و setter-ها در کاتلین آشنا خواهیم شد. پیش از آن که وارد این بحث شویم، باید مطمئن شوید که با مفاهیم کلاس و شیء آشنا هستید. در برنامه‌نویسی getter-ها برای دریافت مقدار یک مشخصه استفاده می‌شوند. به طور مشابه setter-ها نیز برای تنظیم مقادیر مشخصه‌‌ها مورد استفاده قرار می‌گیرند.

ایجاد getter-ها و setter-ها در کاتلین به صورت اختیاری صورت می‌گیرد و در صورتی که آن‌ها را خودتان ایجاد نکنید، به صورت خودکار ایجاد می‌شوند.

طرز کار getter و setter چگونه است؟

به کد زیر در کاتلین توجه کنید:

class Person {
    var name: String = "defaultValue"
}

کد فوق معادل کد زیر است:

class Person {
    var name: String = "defaultValue"

    // getter
    get() = field

    // setter
    set(value) {
        field = value
    }
}

زمانی که یک وهله شیء از کلاس Person ایجاد می‌کنید و مشخصه name را مقداردهی می‌کنید، این مقدار به پارامتر setter مربوط به value ارسال می‌شود و مقدار field به صورت value تعیین می‌شود.

val p = Person()
p.name = "jack"

اکنون زمانی که به مشخصه name شیء دسترسی پیدا کنید، field را به دست می‌آورید، زیرا کدی مانند get() = field وجود دارد:

println("${p.name}")

در ادامه یک مثال عملی را بررسی می‌کنیم:

fun main(args: Array<String>) {

    val p = Person()
    p.name = "jack"
    println("${p.name}")
}

class Person {
    var name: String = "defaultValue"

    get() = field

    set(value) {
        field = value
    }
}

زمانی که برنامه فوق را اجرا کنید، خروجی زیر به دست می‌آید:

Jack

این طرز کار getter و setter به صورت پیش‌فرض است. با این حال می‌توان مقدار مشخصه را با استفاده از getter و setter تغییر نیز داد.

مثالی از تغییر دادن مقدار و مشخصه

fun main(args: Array<String>) {

    val maria = Girl()
    maria.actualAge = 15
    maria.age = 15
    println("Maria: actual age = ${maria.actualAge}")
    println("Maria: pretended age = ${maria.age}")

    val angela = Girl()
    angela.actualAge = 35
    angela.age = 35
    println("Angela: actual age = ${angela.actualAge}")
    println("Angela: pretended age = ${angela.age}")
}

class Girl {
    var age: Int = 0
    get() = field
    set(value) {
        field = if (value < 18)
            18
        else if (value >= 18 && value <= 30)
            value
        else
            value-3
    }

    var actualAge: Int = 0
}

خروجی برنامه فوق به صورت زیر است:

Maria: actual age = 15
Maria: pretended age = 18
Angela: actual age = 35
Angela: pretended age = 32

در کد فوق مشخصه actualAge مطابق انتظار ما عمل می‌کند. با این حال منطقی اضافی نیز وجود دارد که از setter برای اصلاح مقدار مشخصه age استفاده می‌کند.

وراثت در کاتلین

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

وراثت یکی از قابلیت‌های کلیدی برنامه‌نویسی شیءگرا محسوب می‌شود و به کاربران امکان می‌دهد که یک کلاس جدید (کلاس مشتق‌شده) را از کلاس موجود (کلاس مبنا) بسازند. کلاس مشتق‌شده همه قابلیت‌های کلاس مبنا را به ارث می‌برد و می‌تواند برخی قابلیت‌های خاص اضافی نیز برای خود داشته باشد.

چرا باید از وراثت استفاده کنیم؟

فرض کنید می‌خواهید در اپلیکیشن خودتان سه شخصیت به صورت معلم ریاضی، فوتبالیست و تاجر داشته باشید. از آنجا که همه این افراد، صرف‌نظر از شغلشان، انسان هستند، می‌توانند راه رفته و صحبت کنند. با این حال هر کدام از آن‌ها در زمینه شغلی خود به مهارت‌های خاصی نیاز دارند. یک معلم ریاضی می‌تواند به تدریس ریاضیات بپردازد. یک فوتبالیست می‌تواند فوتبال بازی کند، یک تاجر می‌تواند کسب‌وکاری را اداره کند.

شما می‌توانید سه کلاس مجزا برای هر یک از این افراد ایجاد کرده و در آن‌ها قابلیت راه رفتن، صحبت کردن و همچنین مهارت‌های خاص هر یک را قرار دهید.

Kotlin

به این ترتیب در هر یک از این کلاس‌ها باید از کد یکسانی برای راه رفتن و صحبت کردن هر شخصیت استفاده کنید.

همچنین در صورتی که بخواهید قابلیت جدیدی مانند غذا خوردن را به هر یک از این افراد بدهید، باید آن را به صورت مجزا به تک‌تک کاراکترها اضافه کنید. این وضعیت مستعد بروز خطا و همچنین نیازمند نوشتن کدهای تکراری است.

در صورتی که یک کلاس Person داشته باشید که دارای برخی قابلیت‌های اولیه مانند راه رفتن، صحبت کردن، خوردن، خوابیدن باشد و در ادامه برخی مهارت‌های خاص را بنا به نوع شخصیت به هر کدام اضافه کنید، روند کار تا حدود زیادی بهینه می‌شود. این وضعیت به نام وراثت خوانده می‌شود.

Kotlin

بدین ترتیب با بهره‌گیری از وراثت، دیگر لازم نیست کد یکسانی را برای متدهای ()walk() ،talk و ()eat در هر کلاس بنویسید. کافی است این قابلیت‌ها را ارث‌بری کنید.

بنابراین در مورد MathTeacher که یک کلاس مشتق‌شده است، همه قابلیت‌های Person را که کلاس مبنا است به ارث می‌بریم و یک قابلیت جدید به صورت ()teachMath اضافه می‌کنیم. به طور مشابه، در مورد کلاس Footballer، همه قابلیت‌ها را از کلاس Person ارث‌بری می‌کنیم و یک قابلیت جدید به نام ()playFootball نیز اضافه می‌کنیم.

این کار موجب می‌شود که کد تمیزتر شده و قابلیت درک و بسط‌پذیری بالاتری پیدا کند. باید در خاطر داشته باشید که وقتی با وراثت کار می‌کنیم، هر کلاس مشتق‌شده باید یک شرط (is a) ‌داشته باشد که تعیین کنید آیا یک کلاس مبنا است یا نه. در این مثال، MathTeacher یک کلاس Person است. همچنین Footballer یک کلاس مبنا است. اما Businessman یک کلاس مشتق‌شده از Business نیست.

وراثت در کاتلین

اگر بخواهیم مباحث فوق را در کاتلین پیاده‌سازی کنیم به کد زیر می‌رسیم:

open class Person(age: Int) {
    // code for eating, talking, walking
}

class MathTeacher(age: Int): Person(age) {
    // other features of math teacher
}

class Footballer(age: Int): Person(age) {
    // other features of footballer
}

class Businessman(age: Int): Person(age) {
    // other features of businessman
}

در کد فوق Person یک کلاس مبنا است و کلاس‌های MathTeacher ،Footballer و Businessman از کلاس Person مشتق شده‌اند. توجه کنید که کلیدواژه open که پیش از آکولاد در Person آمده، بسیار مهم است.

به طور پیش‌فرض، کلاس‌ها در کاتلین به صورت final هستند. اگر با جاوا آشنا باشید، می‌دانید که یک کلاس final نمی‌تواند کلاس فرعی تولید کند. بنابراین با استفاده از حاشیه‌نویسی open روی یک کلاس به کامپایلر اعلام می‌کنیم که می‌تواند کلاس‌های جدیدی از آن بسازد.

مثالی از وراثت در کاتلین

open class Person(age: Int, name: String) {
    init {
        println("My name is $name.")
        println("My age is $age")
    }
}

class MathTeacher(age: Int, name: String): Person(age, name) {

    fun teachMaths() {
        println("I teach in primary school.")
    }
}

class Footballer(age: Int, name: String): Person(age, name) {
    fun playFootball() {
        println("I play for LA Galaxy.")
    }
}

fun main(args: Array<String>) {
    val t1 = MathTeacher(25, "Jack")
    t1.teachMaths()

    println()

    val f1 = Footballer(29, "Christiano")
    f1.playFootball()
}

خروجی برنامه فوق به صورت زیر است:

My name is Jack.
My age is 25
I teach in primary school.

My name is Cristiano.
My age is 29
I play for LA Galaxy.

در این کد دو کلاس به نام‌های MathTeacher and Footballer از کلاس Person مشتق شده‌اند.

سازنده اولیه کلاس Person دو مشخصه به نام‌های age و name اعلان کرده است و یک بلوک مقداردهی دارد. بلوک مقداردهی (و تابع‌های عضو) کلاس مبنای Person می‌توانند از سوی اشیای کلاس مشتق‌شده یعنی MathTeacher و Footballer مورد دسترسی قرار گیرند.

کلاس‌های مشتق‌شده MathTeacher و Footballer برای خود تابع‌های عضو خاصی به ترتیب به نام‌های ()teachMaths و ()playFootball دارند. این تابع‌ها تنها از سوی اشیا و کلاس متناظرشان قابل دسترسی هستند.

زمانی که شیء t1 از کلاس MathTeacher ایجاد شود:

val t1 = MathTeacher(25, "Jack")

پارامترها به سازنده اولیه ارسال می‌شوند. در کاتلین بلوک init زمانی فراخوانی می‌شود که شیء ایجاد شود. از آنجا که از کلاس Person مشتق شده است، به دنبال بلوک مقداردهی در کلاس مبنا می‌گردد و آن را اجرا می‌کند. اگر MathTeacher دارای بلوک init باشد، کامپایلر نیز می‌تواند بلوک init کلاس مشتق‌شده را اجرا کند.

سپس تابع ()teachMaths برای شیء t1 با استفاده از گزاره ()t1.teachMaths فراخوانی می‌شود. این برنامه مشابه زمانی عمل می‌کند که شیء t1 کلاس Footballer ایجاد می‌شود. این برنامه بلوک init کلاس مبنا را اجرا می‌کند. سپس متد ()playFootball کلاس Footballer با استفاده از گزاره ()f1.playFootball اجرا می‌شود.

نکته‌های مهم در مورد وراثت در کاتلین

اگر کلاس یک سازنده اولیه داشته باشد، مبنا باید با استفاده از پارامترهای سازنده اولیه مقداردهی شود. در برنامه فوق، هر دو کلاس مشتق‌شده دارای پارامترهای age و name هستند و هر دوی این پارامترها در سازنده اولیه کلاس مبنا مقداردهی می‌شوند. به مثال زیر توجه کنید:

open class Person(age: Int, name: String) {
    // some code
}

class Footballer(age: Int, name: String, club: String): Person(age, name) {
    init {
        println("Football player $name of age $age and plays for $club.")
    }

    fun playFootball() {
        println("I am playing football.")
    }
}

fun main(args: Array<String>) {
    val f1 = Footballer(29, "Cristiano", "LA Galaxy")
}

در کد فوق، سازنده اولیه کلاس مشتق‌شده 3 پارامتر دارد و کلاس مبنا دارای 2 پارامتر است. توجه کنید که هر دوی این پارامترهای کلاس مبنا، مقداردهی شده‌اند. در مورد عدم وجود سازنده اولیه، هر کلاس مبنا باید مبنا را مقداردهی کنند یا سازنده دیگری را که این کار را می‌کند نمایندگی کند. به مثال زیر توجه کنید:

fun main(args: Array<String>) {

    val p1 = AuthLog("Bad Password")
}

open class Log {
    var data: String = ""
    var numberOfData = 0
    constructor(_data: String) {

    }
    constructor(_data: String, _numberOfData: Int) {
        data = _data
        numberOfData = _numberOfData
        println("$data: $numberOfData times")
    }
}

class AuthLog: Log {
    constructor(_data: String): this("From AuthLog -> + $_data", 10) {
    }

    constructor(_data: String, _numberOfData: Int): super(_data, _numberOfData) {
    }
}

Override کردن تابع‌های عضو و مشخصه‌ها

اگر کلاس مبنا و کلاس مشتق‌شده شامل یک تابع (یا مشخصه) عضو با نام یکسان باشند، می‌توانید تابع عضو کلاس مشتق‌شده را با استفاده از کلیدواژه open برای تابع عضو کلاس مبنا override کنید.

مثالی از Override کردن تابع عضو

// Empty primary constructor
open class Person() {
    open fun displayAge(age: Int) {
        println("My age is $age.")
    }
}

class Girl: Person() {

    override fun displayAge(age: Int) {
        println("My fake age is ${age - 5}.")
    }
}

fun main(args: Array<String>) {
    val girl = Girl()
    girl.displayAge(31)
}

خروجی برنامه فوق به صورت زیر است:

My fake age is 26.

در برنامه فوق، girl.displayAge(31) متد ()displayAge مربوط به کلاس مشتق‌شده به نام girl.displayAge(31) اقدام به فراخوانی ()displayAge می‌کند. امکان اُوراید کردن کلاس مبنا به روش مشابه نیز وجود دارد.

// Empty primary constructor
open class Person() {
    open var age: Int = 0
        get() = field

        set(value) {
            field = value
        }
}

class Girl: Person() {

    override var age: Int = 0
        get() = field

        set(value) {
            field = value - 5
        }
}

fun main(args: Array<String>) {

    val girl = Girl()
    girl.age = 31
    println("My fake age is ${girl.age}.")
}

خروجی برنامه فوق به صورت زیر است:

My fake age is 26.

چنان که می‌بینید از کلیدواژه‌های override و open برای مشخصه age به ترتیب در کلاس مشتق‌شده و کلاس مبنا استفاده کرده‌ایم.

فراخوانی عضو‌های کلاس مبنا از کلاس مشتق‌شده

امکان فراخوانی تابع‌ها (و مشخصه‌های دسترسی) کلاس مبنا از یک کلاسی مشتق‌شده با استفاده از کلیدواژه super وجود دارد. به مثال زیر توجه کنید:

open class Person() {
    open fun displayAge(age: Int) {
        println("My actual age is $age.")
    }
}

class Girl: Person() {

    override fun displayAge(age: Int) {

        // calling function of base class
        super.displayAge(age)
        
        println("My fake age is ${age - 5}.")
    }
}

fun main(args: Array<String>) {
    val girl = Girl()
    girl.displayAge(31)
}

خروجی برنامه فوق به صورت زیر است:

My age is 31.
My fake age is 26.

مادیفایرهای نمایانی در کاتلین

در این بخش از مقاله آموزش کاتلین، به بررسی 4 مادیفایر نمایانی در کاتلین می‌پردازیم و طرز کار آن‌ها را در سناریوهای مختلف بررسی می‌کنیم.

مادیفایرهای نمایانی کلیدواژه‌هایی هستند که نمایانی (دسترس‌پذیری) کلاس‌ها، شیء‌ها، اینترفیس‌ها، سازه‌ها، تابع‌ها، مشخصه‌ها و setter-های آن‌ها را تنظیم می‌کنند. توجه کنید که امکان تنظیم مادیفایر نمایانی getter-ها وجود ندارد، چون همواره همان نمایانی مشخصه را می‌گیرند.

در بخش‌های قبلی این مقاله با مادیفایرهای نمایانی public و private به صورت اجمالی آشنا شدیم. در این بخش با دو مادیفایر دیگر به نام‌های protected و internal نیز آشنا خواهیم شد.

مادیفایرهای نمایانی درون پکیج

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

مادیفایر توضیح
public اعلان‌ها همه جا نمایان هستند.
private اعلان‌ها فقط درون همان فایل نمایان هستند.
internal اعلان‌ها درون همان ماژول (یک مجموع فایل‌های کاتلین که با هم کامپایل می‌شوند) نمایان هستند.
protected اعلان‌ها در دسترس پکیج‌ها نیستند.

اگر مادیفایر نمایانی تعیین نشود، به صورت پیش‌فرض مقدار public دارد. به مثال زیر توجه کنید:

// file name: hello.kt

package test

fun function1() {}   // public by default and visible everywhere

private fun function2() {}   // visible inside hello.kt

internal fun function3() {}   // visible inside the same module

var name = "Foo"   // visible everywhere
    get() = field   // visible inside hello.kt (same as its property)
    private set(value) {   // visible inside hello.kt
        field = value
    }

private class class1 {}   // visible inside hello.kt

مادیفایرهای نمایانی درون کلاس‌ها و اینترفیس‌ها

طرز کار مادیفایرهای نمایانی برای تابع‌ها و مشخصه‌های عضو اعلان شده درون یک کلاس به صورت زیر است:

مادیفایر توضیح
public در دید هر کلاینتی است که می‌تواند کلاس اعلان کننده را ببیند.
private صرفاً درون همان کلاس نمایان است.
protected درون کلاس و زیرکلاس‌های آن نمایان است.
internal در دید هر کلاینتی درون ماژول است که می‌تواند کلاس اعلان‌کننده را ببیند.

نکته: اگر یک عضو protected را در کلاس مشتق‌شده بدون تعیین نمایانی آن oveeride کنید، نمایانی آن همواره به صورت protected خواهد بود. به مثال زیر توجه کنید:

open class Base() {
    var a = 1                 // public by default
    private var b = 2         // private to Base class
    protected open val c = 3  // visible to the Base and the Derived class
    internal val d = 4        // visible inside the same module

    protected fun e() { }     // visible to the Base and the Derived class
}

class Derived: Base() {

    // a, c, d, and e() of the Base class are visible
    // b is not visible

    override val c = 9        // c is protected
}

fun main(args: Array<String>) {
    val base = Base()

    // base.a and base.d are visible
    // base.b, base.c and base.e() are not visible

    val derived = Derived()
    // derived.c is not visible
}

تغییر دادن نمایانی یک سازنده

نمایانی یک سازنده به صورت پیش‌فرض به صورت public است. با این حال می‌توان این وضعیت را تغییر داد. به این منظور باید یک کلیدواژه constructor به صورت صریح اضافه شود. این سازنده همانند مثال زیر به صورت پیش‌فرض public است:

class Test(val a: Int) {
    // code
}

روش تغییر دادن نمایانی آن به صورت زیر است:

class Test private constructor(val a: Int) {
    // code
}

در مثال فوق، سازنده به صورت private است. توجه کنید که در کاتلین، تابع‌های لوکال، متغیرها و کلاس‌ها نمی‌توانند مادیفایر نمایانی داشته باشند.

کلاس مجرد در کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی «کلاس‌های مجرد» (Abstract Class) در این زبان برنامه‌نویسی می‌پردازیم و شیوه پیاده‌سازی آن‌ها را با معرفی مثال‌هایی توضیح می‌دهیم.

کلیدواژه abstract در کاتلین (همانند جاوا) برای اعلان یک کلاس مجرد مورد استفاده قرار می‌گیرد. از یک کلاس مجرد نمی‌توانید وهله‌هایی بسازید. یعنی امکان ساخت شیء از روی کلاس مجرد وجود ندارد و با این حال می‌توان از روی آن کلاس‌های فرعی ارث‌بری کرد.

تابع‌ها و متدهای عضو یک کلاس مجرد به صورت معمول غیر مجرد هستند، مگر این که به صورت صریح از کلیدواژه abstract استفاده کنید و آن‌ها را به صورت مجرد درآورید. به مثال زیر توجه کنید:

abstract class Person {
    
    var age: Int = 40

    fun displaySSN(ssn: Int) {
        println("My SSN is $ssn.")
    }

    abstract fun displayJob(description: String)
}
  • در کد فوق، یک کلاس مجرد به نام Person ایجاد شده است. امکان ساخت شیء از روی این کلاس وجود ندارد.
  • این کلاس یک هیچ مشخصه غیر مجرد به نام age و یک متد غیر مجرد به نام ()displaySSN دارد. اگر بخواهید این اعضا را در یک زیرکلاس override کنید، باید آن‌ها را با کلیدواژه‌های open نشانه‌گذاری نمایید.
  • این کلاس یک متد مجرد به نام ()displayJob دارد. این متد هیچ پیاده‌سازی ندارد و باید در زیرکلاس‌های مربوطه override شود.

توجه کنید که کلاس‌های مجرد همواره open هستند. از این رو لزومی به استفاده از کلیدواژه open برای ارث‌بری زیرکلاس‌ها از آن وجود ندارد.

مثالی از کلاس و متد مجرد در کاتلین

abstract class Person(name: String) {

    init {
        println("My name is $name.")
    }

    fun displaySSN(ssn: Int) {
        println("My SSN is $ssn.")
    }

    abstract fun displayJob(description: String)
}

class Teacher(name: String): Person(name) {

    override fun displayJob(description: String) {
        println(description)
    }
}

fun main(args: Array<String>) {
    val jack = Teacher("Jack Smith")
    jack.displayJob("I'm a mathematics teacher.")
    jack.displaySSN(23123)
}

خروجی برنامه فوق به صورت زیر است:

My name is Jack Smith.
I'm a mathematics teacher.
My SSN is 23123.

در برنامه فوق یک کلاس به نام Teacher از یک کلاس مجرد به نام Person مشتق شده است. در ادامه یک شیء به نام jack نیز از کلاس Teacher وهله‌سازی شده است. ما رشته “Jack Smith” را به عنوان یک پارامتر به سازنده اولیه در زمان ایجاد شیء ارسال کرده‌ایم. این کار موجب می‌شود که بلوک مقداردهی کلاس Person اجرا شود.

سپس متد ()displayJob با استفاده از شیء jack فراخوانی شده است. توجه کنید که متد ()displayJob در کلاس مبنا به صورت مجرد اعلان شده است و در کلاس متشق‌شده نیز override گشته است.

در نهایت متد ()displaySSN با استفاده از شیء jack فراخوانی شده است. این متد غیر مجرد است و در کلاس Person و نه در کلاس Techer اعلان یافته است.

توجه کنید که اینترفیس‌های کاتلین مشابه کلاس‌های مجرد هستند. با این حال، اینترفیس‌ها نمی‌توانند حالت را ذخیره کنند، در حالی که کلاس مجرد چنین قابلیتی دارد.

معنی این حرف آن است که اینترفیس ممکن است مشخصه داشته باشد، اما باید مجرد باشد یا پیاده‌سازی accessor ارائه کند. از سوی دیگر لزومی برای مجرد بودن مشخصه‌ها در یک کلاس مجرد وجود ندارد.

اینترفیس‌های کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی اینترفیس‌ها در کاتلین پرداخته و شیوه پیاده‌سازی آن‌ها را در این زبان برنامه‌نویسی به همراه مثال‌هایی معرفی می‌کنیم.

اینترفیس‌های کاتلین مشابه اینترفیس‌های جاوا 8 هستند. این اینترفیس‌ها می‌توانند شامل تعاریف متدهای مجرد و همچنین پیاده‌سازی‌های متدهای غیر مجرد باشند. با این حال، نمی‌‌توانند «حالت» (State) داشته باشند.

معنی این حرف آن است که اینترفیس می‌تواند مشخصه داشته باشد، اما این مشخصه‌ها باید مجرد باشند یا برای آن‌ها accessor پیاده‌سازی شده باشد. چنان که در بخش قبلی مقاله توضیح دادیم، کلاس‌های مجرد مشابه اینترفیس هستند و تنها یک تفاوت دارند و آن این است که لزومی به مجرد بودن مشخصه‌ها یا وجود accessor نیست.

شیوه تعریف اینترفیس چگونه است؟

برای تعریف اینترفیس در کاتلین از کلیدواژه interface استفاده می‌کنیم. به مثال زیر توجه کنید:

interface MyInterface {

    var test: String   // abstract property

    fun foo()          // abstract method
    fun hello() = "Hello there" // method with default implementation
}

در کد فوق، ابتدا یک اینترفیس به نام MyInterface ایجاد شده است. این اینترفیس یک مشخصه مجرد به نام test و یک متد مجرد به نام ()foo دارد. این اینترفیس یک متد غیر مجرد به نام ()hello دارد.

شیوه پیاده‌سازی اینترفیس چگونه است؟

روش پیاده‌سازی یک کلاس یا شیء در کاتلین به صورت زیر است:

interface MyInterface {

    val test: Int   // abstract property

    fun foo() : String   // abstract method (returns String)
    fun hello() {   // method with default implementation
        // body (optional)
    }
}

class InterfaceImp : MyInterface {

    override val test: Int = 25
    override fun foo() = "Lol"

    // other code
}

در کد فوق یک کلاس به نام InterfaceImp اینترفیس MyInterface را پیاده‌سازی می‌کند. این کلاس اعضای مجرد (مشخصه test و متد foo) مربوط به اینترفیس را override می‌کند.

طرز کار اینترفیس چگونه است؟

interface MyInterface {

    val test: Int

    fun foo() : String

    fun hello() {
        println("Hello there, pal!")
    }
}

class InterfaceImp : MyInterface {

    override val test: Int = 25
    override fun foo() = "Lol"

}

fun main(args: Array<String>) {
    val obj = InterfaceImp()

    println("test = ${obj.test}")
    print("Calling hello(): ")

    obj.hello()

    print("Calling and printing foo(): ")
    println(obj.foo())
}

خروجی برنامه فوق به صورت زیر است:

test = 25
Calling hello(): Hello there, pal!
Calling and printing foo(): Lol

چنان که پیش‌تر اشاره کردیم، یک اینترفیس می‌تواند یک مشخصه داشته باشد که accessor را پیاده‌سازی کند. به مثال زیر توجه کنید:

interface MyInterface {

    // property with implementation
    val prop: Int
        get() = 23
}

class InterfaceImp : MyInterface {
    // class body
}

fun main(args: Array<String>) {
    val obj = InterfaceImp()

    println(obj.prop)
}

خروجی برنامه فوق به صورت زیر است:

23

در کد فوق، prop مجرد نیست. با این حال، درون اینترفیس مجاز است، زیرا پیاده‌سازی accessor را ارائه می‌کند. با این حال، نمی‌توان کدی مانند val prop: Int = 23 در اینترفیس نوشت.

پیاده‌سازی دو یا چند اینترفیس در یک کلاس

کاتلین امکان وراثت چندگانه را فراهم نساخته است. با این حال، امکان پیاده‌سازی دو یا چند اینترفیس در یک کلاس منفرد وجود دارد. به مثال زیر توجه کنید:

interface A {

    fun callMe() {
        println("From interface A")
    }
}

interface B  {
    fun callMeToo() {
        println("From interface B")
    }
}

// implements two interfaces A and B
class Child: A, B

fun main(args: Array<String>) {
    val obj = Child()

    obj.callMe()
    obj.callMeToo()
}

خروجی برنامه فوق به صورت زیر است:

From interface A

From interface B

حل تعارض‌های override کردن (اینترفیس‌های چندگانه)

فرض کنید دو اینترفیس A و B یک متد غیر مجرد با نام یکسان مانند ()callMe دارند. ما این دو اینترفیس را در یک کلاس مانند C پیاده‌سازی می‌کنیم. حال اگر متد ()callMe با استفاده از شیء کلاس C فراخوانی شود، کامپایلر یک خطا تولید می‌کند. به مثال زیر توجه کنید:

interface A {

    fun callMe() {
        println("From interface A")
    }
}

interface B  {
    fun callMe() {
        println("From interface B")
    }
}

class Child: A, B 

fun main(args: Array<String>) {
    val obj = Child()

    obj.callMe()
}

خطای کد فوق به صورت زیر است:

Error: (14, 1) Kotlin: Class 'C' must override public open fun callMe(): Unit defined in A because it inherits multiple interface methods of it

برای حل این مشکل، باید پیاده‌سازی خاص خود را ارائه کنیم:

interface A {

    fun callMe() {
        println("From interface A")
    }
}

interface B  {
    fun callMe() {
        println("From interface B")
    }
}

class C: A, B {
    override fun callMe() {
        super<A>.callMe()
        super<B>.callMe()
    }
}

fun main(args: Array<String>) {
    val obj = C()

    obj.callMe()
}

اکنون خروجی برنامه به صورت زیر درمی‌‌آید:

From interface A

From interface B

این پیاده‌سازی صریح متد ()callMe است که در کلاس C ارائه شده است.

class C: A, B {
    override fun callMe() {
        super<A>.callMe()
        super<B>.callMe()
    }
}

گزاره ()super<A>.callMe متد ()callMe مربوط به کلاس A را فرا می‌خواند. به طور مشابه، ()super<B>.callMe متد ()callMe مربوط به کلاس B را فراخوانی می‌کند.

کلاس‌های تودرتو و داخلی در کاتلین

در این بخش از مقاله آمورش کاتلین به بررسی کلاس‌های تودرتو و داخلی به کمک مثال‌های مختلف می‌پردازیم.

کلاس تودرتو در کاتلین

کاتلین همانند جاوا امکان تعریف یک کلاس درون کلاس دیگر را به صورت کلاس تودرتو فراهم ساخته است.

class Outer {
    ... .. ...
    class Nested {
        ... .. ...
    }
}

از آنجا که کلاس nested یک عضو از کلاس بیرونی خود به نام Outer است، می‌توانید با استفاده از نماد نقطه (.) به کلاس Nested و اعضای آن دسترسی داشته باشید.

مثالی از کلاس تودرتوی کاتلین

class Outer {

    val a = "Outside Nested class."

    class Nested {
        val b = "Inside Nested class."
        fun callMe() = "Function call from inside Nested class."
    }
}

fun main(args: Array<String>) {
    // accessing member of Nested class
    println(Outer.Nested().b)

    // creating object of Nested class
    val nested = Outer.Nested()
    println(nested.callMe())
}

خروجی برنامه فوق به صورت زیر است:

Inside Nested class.

Function call from inside Nested class.

توجه کنید که کلاس تودرتو در کاتلین مشابه کلاس تودرتوی استاتیک در جاوا است. در زبان جاوا زمانی که یک کلاس را درون کلاس دیگری اعلان می‌کنید، به صورت پیش‌فرض به صورت یک کلاس درونی درمی‌آید. با این حال در کاتلین باید از مادیفایر inner برای ایجاد کلاس درونی استفاده کنید. در این خصوص در ادامه بیشتر توضیح خواهیم داد.

کلاس‌های درونی در کاتلین

کلاس‌های تودرتوی در کاتلین به وهله کلاس بیرونی دسترسی ندارند. به مثال زیر توجه کنید:

class Outer {
    val foo = "Outside Nested class."

    class Nested {
        // Error! cannot access member of outer class.
        fun callMe() = foo
    }
}

fun main(args: Array<String>) {

    val outer = Outer()
    println(outer.Nested().callMe())
}

کد فوق کامپایل نمی‌شود، زیرا تلاش کردیم که به مشخصه foo کلاس Outer از درون کلاس Nested دسترسی پیدا کنیم.

برای حل این مشکل، باید کلاس تودرتو را با کلیدواژه inner نشانه‌گذاری کنید تا یک کلاس درونی ایجاد شود. کلاس‌های درونی یک ارجاع به کلاس بیرونی دارند و می‌توانند به اعضای کلاس بیرونی دسترسی داشته باشند.

مثالی از کلاس درونی در کاتلین

class Outer {

    val a = "Outside Nested class."

    inner class Inner {
        fun callMe() = a
    }
}

fun main(args: Array<String>) {

    val outer = Outer()
    println("Using outer object: ${outer.Inner().callMe()}")

    val inner = Outer().Inner()
    println("Using inner object: ${inner.callMe()}")
}

خروجی برنامه فوق به صورت زیر است:

Using outer object: Outside Nested class.

Using inner object: Outside Nested class.

کلاس داده در کاتلین

در این بخش از مقاله آموزش کاتلین با روش ایجاد کلاس‌های داده در کاتلین آشنا خواهیم شد. همچنین به بررسی الزاماتی که کلاس داده باید داشته باشد و کارکردهای استاندارد آن می‌پردازیم.

در پاره‌ای موارد لازم است که یک کلاس صرفاً برای نگهداری داده‌ها ایجاد شود. در یک چنین موقعیت‌هایی باید کلاس را به صورت data نشانه‌گذاری کنیم تا یک کلاس داده ایجاد شود. به مثال زیر توجه کنید.

data class Person(val name: String, var age: Int)

کامپایلر در مورد این کلاس، موارد زیر را به صورت خودکار تولید می‌کند:

  • تابع ()copy، جفت ()equals و ()hashCode و ()toString از سازنده اولیه ایجاد می‌شوند.
  • تابع‌های ()componentN نیز ساخته می‌شوند.

پیش از آن که به بررسی تفصیلی این قابلیت‌ها بپردازیم، باید در مورد الزاماتی که کلاس داده باید داشته باشد صحبت کنیم.

الزامات کلاس داده در کاتلین

الزاماتی که کلاس داده باید برآورده سازد به شرح زیر هستند:

  • سازنده اولیه باید دست کم یک پارامتر داشته باشد.
  • پارامترهای سازنده اولیه باید به صورت val (صرفا-خواندنی) یا var (خواندن-نوشتن) نشانه‌گذاری شوند.
  • کلاس نمی‌تواند به صورت open ،abstract ،inner و یا sealed باشد.
  • کلاس می‌تواند کلاس‌های دیگر را بسط دهد یا اینترفیس‌ها را پیاده‌سازی کند. اگر از نسخه 1.1 کاتلین استفاده می‌کنید،، کلاس می‌تواند تنها اینترفیس‌ها را پیاده‌سازی کند.

مثالی از کلاس داده در کاتلین

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val jack = User("jack", 29)
    println("name = ${jack.name}")
    println("age = ${jack.age}")
}

خروجی برنامه فوق به صورت زیر است:

name = jack
age = 29

زمانی که یک کلاس داده را اعلان می‌کنیم، کامپایلر به صورت خودکار چند تابع مانند ()toString() ،equals() ،hashcode و غیره را در پس‌زمینه تولید می‌کند. این کار به حفظ انسجام کد کمک می‌کند. اگر با جاوا کار کرده باشید، می‌دانید که برای اجرای این کارکردها مقدار زیادی کد قالبی بنویسید. در ادامه با روش استفاده از این تابع‌ها آشنا می‌شویم:

Copying

در مورد یک کلاس داده، می‌توانیم یک کپی از یک شیء را با تغییر در برخی از مشخصه‌های آن با استفاده تابع ()copy ایجاد کنیم. طرز کار آن به صورت زیر است:

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val u1 = User("John", 29)
   
    // using copy function to create an object
    val u2 = u1.copy(name = "Randy")

    println("u1: name = ${u1.name}, name = ${u1.age}")
    println("u2: name = ${u2.name}, name = ${u2.age}")
}

خروجی برنامه فوق به صورت زیر است:

u1: name = John, name = 29
u2: name = Randy, name = 29

تابع ()toString یک بازنمایی رشته‌ای از یک شیء بازگشت می‌دهد:

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val u1 = User("John", 29)
    println(u1.toString())
}

خروجی برنامه فوق به صورت زیر است:

User(name=John, age=29)

()hashCode و ()equals

متد ()hasCode کد هش یک شیء را بازگشت می‌دهد. اگر دو شیء برابر باشند، ()hashCode نتیجه صحیح یکسانی را تولید می‌کند. در صورتی که دو شیء برابر باشند، متد ()equals مقدار true بازگشت می‌دهد. اگر اشیا برابر نباشند، متد ()equals مقدار false بازگشت می‌دهد.

data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
    val u1 = User("John", 29)
    val u2 = u1.copy()
    val u3 = u1.copy(name = "Amanda")

    println("u1 hashcode = ${u1.hashCode()}")
    println("u2 hashcode = ${u2.hashCode()}")
    println("u3 hashcode = ${u3.hashCode()}")

    if (u1.equals(u2) == true)
        println("u1 is equal to u2.")
    else
        println("u1 is not equal to u2.")

    if (u1.equals(u3) == true)
        println("u1 is equal to u3.")
    else
        println("u1 is not equal to u3.")
}

خروجی برنامه فوق به صورت زیر است:

u1 hashcode = 71750738
u2 hashcode = 71750738
u3 hashcode = 771732263
u1 is equal to u2.
u1 is not equal to u3.

تخریب اعلان‌ها

امکان تخریب یک شیء با صورت چند متغیر با استفاده از اعلان‌های destructing وجود دارد. به مثال زیر توجه کنید:

data class User(val name: String, val age: Int, val gender: String)

fun main(args: Array<String>) {
    val u1 = User("John", 29, "Male")

    val (name, age, gender) = u1
    println("name = $name")
    println("age = $age")
    println("gender = $gender")
}

خروجی برنامه فوق به صورت زیر است:

name = John
age = 29
gender = Male

این روش به این دلیل ممکن است که کامپایلر همه مشخصه‌های تابع‌های ()componentN را برای کلاس داده تولید می‌کند. به مثال زیر توجه کنید:

data class User(val name: String, val age: Int, val gender: String)

fun main(args: Array<String>) {
    val u1 = User("John", 29, "Male")

    println(u1.component1())     // John
    println(u1.component2())     // 29  
    println(u1.component3())     // "Male"
}

خروجی برنامه فوق به صورت زیراست:

John
29
Male

کلاس‌های Sealed در کاتلین

در این بخش از مقاله آموزش کاتلین در مورد کلاس‌های Sealed، شیوه ایجاد و زمان استفاده از آن‌ها را به همراه مثال بررسی می‌کنیم.

کلاس‌های Sealed زمانی استفاده می‌شوند که تنها یکی از انواع از میان مجموعه محدود را داشته باشد. پیش از بررسی جزییات کلاس‌های Sealed ابتدا به بررسی مسائلی که آن‌ها حل می‌کنند‌، می‌پردازیم. به مثال زیر توجه کنید:

class Expr
class Const(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
        when (e) {
            is Const -> e.value
            is Sum -> eval(e.right) + eval(e.left)
            else ->
                throw IllegalArgumentException("Unknown expression")
        }

در برنامه فوق، کلاس مبنای Expr کلاس مشتق‌شده به نام‌های Const (نمایش یک عدد) و sum (نمایش مجموع دو عبارت) دارد. در این حالت استفاده از شاخه else برای شرط پیش‌فرض در عبارت when ضروری است.

اکنون اگر یک زیرکلاس از کلاس Expr داشته باشیم، کامپایلر هیچ چیزی را تشخیص نمی‌دهد، زیرا شاخه else آن را مدیریت می‌کند که می‌تواند منجر به بروز باگ شود. با این حال، در صورتی که کامپایلر در زمان افزودن یک زیرکلاس جدید خطایی تولید کند، بهتر خواهد بود.

برای حل این مسئله، می‌توانید از کلاس‌های sealed استفاده کنید. چنان که اشاره کردیم، کلاس sealed امکان ایجاد زیرکلاس را محدود می‌سازد. همچنین زمانی که همه زیرکلاس‌های یک کلاس sealed به صورت یک عبارت when باشند، لزومی به استفاده از شاخه else نخواهد بود.

برای ایجاد یک کلاس sealed، باید از مادیفایر sealed مانند مثال زیر استفاده کنید:

sealed class Expr

مثالی از کلاس Sealed

در ادامه شیوه حل مسئله فوق را با استفاده از کلاس Sealed بررسی می‌کنیم.

sealed class Expr
class Const(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
object NotANumber : Expr()


fun eval(e: Expr): Int =
        when (e) {
            is Const -> e.value
            is Sum -> eval(e.right) + eval(e.left)
            NotANumber -> java.lang.Double.NaN
        }

چنان که دیدیم هیچ شاخه else وجود ندارد. اگر یک زیرکلاس جدید از کلاس Expr اشتقاق بدهیم، کامپایلر خطا تولید می‌کند، مگر این که زیرکلاس در عبارت when مدیریت شود.

چند نکته مهم

  • همه زیرکلاس‌های یک کلاس Sealed باید در همان فایل اعلان شوند که کلاس Sealed اعلان شده است.
  • یک کلاس Sealed خودش مجرد است و نمی‌تواند وهله‌هایی از شیء از روی آن بسازد.
  • امکان ایجاد سازنده‌های غیر خصوصی از یک کلاس Sealed وجود ندارد، چون سازنده‌های آن‌ها به صورت پیش‌فرض private هستند.

تفاوت بین Enum و کلاس Sealed

کلاس enum و کلاس Selaed کاملاً مشابه هستند. مجموعه مقادیر یک نوع enum نیز مانند یک کلاس sealed محدود هستند. تنها تفاوت در این است که نوع enum تنها می‌تواند یک وهله منفرد ایجاد کند، در حالی که یک زیرکلاس از یک کلاس sealed می‌تواند چند وهله داشته باشد.

اعلان‌ها و عبارت‌های شیء در کاتلین

در این بخش از مقاله آموزش کاتلین به بررسی مفهوم اعلان شیء (سینگلتون) و عبارت شیء به همراه معرفی برخی مثا‌ل‌ها خواهیم پرداخت.

اعلان‌های شیء

«سینگلتون» (Singleton)‌ یک الگوی شیء‌گرایی است که در آن یک کلاس می‌تواند تنها یک وهله (شیء) داشته باشد. برای نمونه تصور کنید مشغول کار روی یک اپلیکیشن هستید که از پایگاه داده SQL استفاده می‌کند. همچنین می‌خواهید یک «استخر اتصال» (Connection Pool) به پایگاه داده ایجاد کنید تا از اتصال یکسانی برای همه کلاینت‌ها بهره بگیرید. به این منظور می‌توانید یک اتصال از طریق سینگلتون ایجاد کنید، به طوری که هر کلاینت بتواند اتصال یکسانی به دست آورد.

کاتلین روش آسانی برای ایجاد سینگلتون با استفاده از قابلیت اعلان شیء فراهم ساخته است. به این منظور باید از کلیدواژه object استفاده کنیم.

object SingletonExample {
    ... .. ...
    // body of class
    ... .. ...
}

کد فوق یک اعلان کلاس و یک اعلان از وهله منفرد SingletonExample برای کلاس بازگشت می‌‌دهد. اعلان شیء می‌تواند شامل مشخصه‌ها، متدها و مواردی از این دست باشد. با این حال، امکان داشتن سازنده ندارند. دلیل این امر آن است که مشابه اشیای یک کلاس نرمال، می‌توانیم متدها را با استفاده از نماد نقطه (.) فراخوانی کرده و به مشخصه‌ها دسترسی داشته باشیم.

مثالی از اعلان شیء

object Test {
    private var a: Int = 0
    var b: Int = 1

    fun makeMe12(): Int {
        a = 12
        return a
    }
}

fun main(args: Array<String>) {
    val result: Int

    result = Test.makeMe12()

    println("b = ${Test.b}")
    println("result = $result")
}

خروجی برنامه فوق به صورت زیر است:

b = 1
result = 12

اعلان شیء می‌تواند از کلاس‌ها و اینترفیس‌ها به روشی مشابه کلاس‌های نرمال ارث‌بری کند.

سینگلتون‌ها و تزریق وابستگی

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

عبارت‌های شیء در کاتلین

کلیدواژه object می‌تواند برای ایجاد اشیا از یک کلاس بی‌نام به صورت شیء بی‌نام مورد استفاده قرار گیرد. از عبارت‌های شیء در مواردی استفاده می‌شود که بخواهیم یک شیء را با کمی دست‌کاری از روی یک کلاس یا اینترفیس بسازیم و قصد ساخت یک زیرکلاس از آن نیز نداشته باشیم. به مثال زیر توجه کنید:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }

    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})

در مثال فوق، شیء بی‌نام با بسط کلاس MouseAdapter اعلان شده است. برنامه فوق دو متد MouseAdapter را به نام‌های ()mouseClicked و ()mouseEntered باطل می‌کند. در صورت لزوم، می‌توانیم یک نام به شیء بی‌نام بدهیم و آن را در یک متغیر ذخیره کنیم. به مثال زیر توجه کنید:

val obj = object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }

    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
}

مثالی از عبارت شیء در کاتلین

open class Person() {
    fun eat() = println("Eating food.")

    fun talk() = println("Talking with people.")

    open fun pray() = println("Praying god.")
}

fun main(args: Array<String>) {
    val atheist = object : Person() {
        override fun pray() = println("I don't pray. I am an atheist.")
    }

    atheist.eat()
    atheist.talk()
    atheist.pray()
}

خروجی برنامه فوق به صورت زیر است:

Eating food.
Talking with people.
I don't pray. I am an atheist.

در برنامه فوق، شیء بی‌نام در متغیر atheist ذخیره می‌شود که کلاس Person را پیاده‌سازی می‌کند که متد ()pray را باطل ساخته است. اگر قصد دارید یک کلاس را که سازنده دارد، برای اعلان یک شیء بی‌نام اعلان کنید، باید پارامترهای مناسبی به سازنده ارسال نمایید. به مثال زیر توجه کنید:

open class Person(name: String, age: Int) {

    init {
        println("name: $name, age: $age")
    }

    fun eat() = println("Eating food.")
    fun talk() = println("Talking with people.")
    open fun pray() = println("Praying god.")
}

fun main(args: Array<String>) {
    val atheist = object : Person("Jack", 29) {
        override fun pray() = println("I don't pray. I am an atheist.")
    }

    atheist.eat()
    atheist.talk()
    atheist.pray()
}

خروجی این برنامه به صورت زیر است:

name: Jack, age: 29
Eating food.
Talking with people.
I don't pray. I am an atheist.

اشیای Companion در کاتلین

در این بخش از مقاله آموزش کاتلین به کمک برخی مثال‌ها به بررسی اشیای Companion در این زبان برنامه‌نویسی خواهیم پرداخت. پیش از آن که در مورد اشیای Companion صحبت کنیم، به بررسی مثالی از دسترس به اعضای یک کلاس می‌پردازیم.

class Person {
    fun callMe() = println("I'm called.")
}

fun main(args: Array<String>) {
    val p1 = Person()
    
    // calling callMe() method using object p1
    p1.callMe()    
}

در مثال فوق یک شیء به نام p1 از کلاس Pedrson ایجاد کردیم تا متد ()callMe را فراخوانی کنیم. طرز کار اشیا به صورت معمول این گونه است. با این حال، در کاتلین می‌توان متد ()callMe را با استفاده از کلاس نام نیز فراخوانی کرد که در این مورد Person است. به این منظور باید یک شیء Companion را با استفاده از کلیدواژه companion روی اعلان شیء ایجاد کنیم.

مثالی از اشیای companion

class Person {
    companion object Test {
        fun callMe() = println("I'm called.")
    }
}

fun main(args: Array<String>) {
    Person.callMe()
}

خروجی برنامه فوق به صورت زیر است:

I'm called.

در برنامه فوق، اعلان شیء Test با کلیدواژه companion نشانه‌گذاری شده است تا یک شیء companion ایجاد شود. از این رو امکان فراخوانی متد ()callMe با استفاده از نام کلاس به صورت زیر وجود دارد:

Person.callMe()

نام شیء companion اختیاری است و می‌تواند نادیده گرفته شود.

class Person {
    
    // name of the companion object is omitted
    companion object {
        fun callMe() = println("I'm called.")
    }
}

fun main(args: Array<String>) {
    Person.callMe()
}

اگر با جاوا آشنا باشید، می‌توانید اشیای companion را به متدهای استاتیک تشبیه کنید، هر چند طرز کار آن‌ها به صورت داخلی کاملاً متفاوت است. اشیای companion می‌توانند به اعضای خصوصی کلاس دسترسی داشته باشند. از این رو می‌توان از آن‌ها برای پیاده‌سازی الگوهای متد فکتوری استفاده کرد.

تابع بسط (Exteension) ‌در کاتلین

در این بخش از مقاله آموزش کاتلین با روش بسط یک کلاس با کارکردهای جدید با استفاده از تابع‌های بسط آشنا خواهیم شد. فرض کنید لازم است یک کلاس را با کارکرد جدیدی بسط دهید. در اغلب زبان‌های برنامه‌نویسی به این منظور یک کلاس جدید مشتق می‌شود یا از نوع الگوی طراحی به این منظور بهره می‌گیرند. با این حال، در کاتلین از تابع Extension برای بسط یک کلاس با کارکردهای جدید استفاده می‌کنیم. تابع بسط اساس یک تابع عضو کلاس است که در خارج از کلاس تعریف می‌شود.

برای نمونه فرض کنید لازم است یک متد جدید در کلاس String داشته باشید که یک رشته جدید را با حذف کاراکترهای اول و آخر رشته ارائه شده بازگشت دهد. این متد از قبل در کلاس String وجود ندارد و باید از تابع Extension برای اجرای این وظیفه استفاده کنیم.

مثالی از حذف کاراکتر اول و آخر از رشته

fun String.removeFirstLastChar(): String =  this.substring(1, this.length - 1)

fun main(args: Array<String>) {
    val myString= "Hello Everyone"
    val result = myString.removeFirstLastChar()
    println("First character is: $result")
}

خروجی برنامه فوق به صورت زیر است:

First character is: ello Everyon

اگر برنامه فوق تابع بسط First character is: ello Everyon به کلاس String اضافه شده است. نام کلاس، نوع دریافتی (در این مورد String) است. کلیدواژه this درون تابع بسط به شیء دریافت‌کننده اشاره دارد.

آموزش کاتلین

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

Overload کردن عملگر در کاتلین

در این مقاله به بررسی روش overload کردن عملگر در کاتلین به کمک برخی مثال‌ها می‌پردازیم. زمانی که از یک عملگر در کاتلین استفاده می‌کنیم، تابع عضو متناظر آن فراخوانی می‌شود. برای نمونه عبارت a+b در پس‌زمینه به a.plus(b) ترجمه می‌شود.

fun main(args: Array<String>) {
    val a = 5
    val b = 10

    print(a.plus(b)) // print(a+b)
}

خروجی برنامه فوق به صورت زیر است:

15

در واقع تابع ()plus برای کار با انواع مقدماتی مختلف کاتلین و String به صورت overload درآمده است.

// + operator for basic types
operator fun plus(other: Byte): Int
operator fun plus(other: Short): Int
operator fun plus(other: Int): Int
operator fun plus(other: Long): Long
operator fun plus(other: Float): Float
operator fun plus(other: Double): Double

// for string concatenation
operator fun String?.plus(other: Any?): String

همچنین امکان تعریف طرز کار عملگر برای اشیای مختلف از طریق overload کردن تابع‌های متناظر آن‌ها نیز وجود دارد. برای نمونه باید طرز کار عملگر + را از طریق overload کردن تابع ()plus برای اشیای مختلف تعریف کنیم.

مثالی از overload کردن عملگر +

fun main(args: Array<String>) {
    val p1 = Point(3, -8)
    val p2 = Point(2, 9)

    var sum = Point()
    sum = p1 + p2

    println("sum = (${sum.x}, ${sum.y})")
}

class Point(val x: Int = 0, val y: Int = 10) {

    // overloading plus function
    operator fun plus(p: Point) : Point {
        return Point(x + p.x, y + p.y)
    }
}

خروجی برنامه فوق به صورت زیر است:

sum = (5, 1)

در برنامه فوق تابع ()plus با کلیدواژه operator نشانه‌گذاری شده است تا به کامپایلر اعلام شود که عملگر + به صورت overload شده درآمده است. عبارت p1+p2 در پس‌زمینه به صورت p1.plus(p2) ترجمه می‌شود.

مثالی از overload کردن عملگر —

در این بخش با مثالی از overload کردن عملگر — آشنا خواهیم شد. عبارت a- در پس‌زمینه به صورت ()a.dec ترجمه می‌شود. تابع عضو ()dec هیچ آرگومانی نمی‌گیرد.

fun main(args: Array<String>) {
    var point = Point(3, -8)
    --point

    println("point = (${point.x}, ${point.y})")
}

class Point(var x: Int = 0, var y: Int = 10) {
    operator fun dec() = Point(--x, --y)
}

خروجی برنامه فوق به صورت زیر است:

point = (2, -9)

به خاطر داشته باشید که گزاره زیر:

operator fun dec() = Point(--x, --y)

معادل گزاره زیر است:

operator fun dec(): Point {
    return Point(--x, --y)
}

چند نکته مهم در مورد overload کردن عملگرها

زمانی که عملگرها را overload می‌کنیم، باید مقصود اصلی عملگر حفظ شود. به مثال زیر توجه کنید:

fun main(args: Array<String>) {
    val p1 = Point(3, -8)
    val p2 = Point(2, 9)

    var sum = Point()
    sum = p1 + p2

    println("sum = (${sum.x}, ${sum.y})")
}

class Point(val x: Int = 0, val y: Int = 10) {

    // overloading plus function
    operator fun plus(p: Point) = Point(x - p.x, y - p.y)
}

با این که برنامه فوق از نظر فنی صحیح است، اما از عملگر + برای تفریق مشخصه‌های متناظر دو شیء استفاده شده است که موجب ایجاد سردرگمی در برنامه می‌شود. برخلاف زبان‌هایی مانند اسکالا، تنها مجموعه خاصی از عملگرها در کاتلین می‌توانند overload شوند.

جمع‌بندی آموزش کاتلین

به این ترتیب به آخرین بخش این مقاله با موضوع آموزش کاتلین می‌رسیم. زبان برنامه‌نویسی کاتلین در حال حاضر از سوی شرکت‌های زیادی از قبیل Netflix ،Pinterest و Corda برای ساخت اپلیکیشن‌های قدرتمند اندروید مورد استفاده قرار می‌گیرد. در طی همان مدت کوتاه چهار ساله از زمان معرفی کاتلین، این زبان محبوبیت زیادی یافته و قابلیت‌های برنامه‌نویسی بسیاری به آن اضافه شده است. پیش‌بینی می‌شود که در طی سال‌های آتی از کاتلین برای توسعه بازی‌های چندپلتفرمی و همچنین توسعه اپلیکیشن‌های چندپلتفرمی استفاده شود. از سوی دیگر استفاده از کاتلین در بخش اسکریپت‌نویسی سمت سرور و میکروسرویس‌ها و همچنین یادگیری ماشین و تحلیل داده رو به فزونی می‌رود.

سخن پایانی

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

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

بر اساس رای 6 نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

«میثم لطفی» در رشته‌های ریاضیات کاربردی و مهندسی کامپیوتر به تحصیل پرداخته و شیفته فناوری است. وی در حال حاضر علاوه بر پیگیری علاقه‌مندی‌هایش در رشته‌های برنامه‌نویسی، کپی‌رایتینگ و محتوای چندرسانه‌ای، در زمینه نگارش مقالاتی با محوریت نرم‌افزار با مجله فرادرس همکاری دارد.

3 نظر در “آموزش کاتلین (Kotlin) — جامع و رایگان | از صفر تا صد

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *