چرا از جاوا به زبان کاتلین (Kotlin) سوئیچ کنیم؟

نویسنده : سید ایوب کوکبی ۱۷ اردیبهشت ۱۳۹۷

چرا از جاوا به زبان کاتلین (Kotlin) سوئیچ کنیم؟

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

این زبان توسط شرکت JetBrains ساخته شده و این حقیقت را که محصولات ارزشمند و محبوب این شرکت از جمله IntelliJ و Resharper تا این حد کاتلین را بر سر زبان‌ها آورده نمی‌توان نادیده گرفت. این زبان ذاتا پراگماتیک و جمع‌وجور بوده و کدنویسی با آن تجربه‌ی لذت‌بخش و شیرینی است. اگرچه کاتلین به هر دو زبان جاوا اسکریپت و زبان ماشین قابل کامپایل است ولی تمرکز ما در این مقاله محیط اصلی این زبان یعنی JVM است. در ادامه سعی می‌کنیم تا با ذکر دلایلی شما را مجاب به استفاده از این زبان کنیم:

۰- سازگار با زبان جاوا

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

۱- سینتکس آشنا

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

class Foo {

val b: String = “b” // val means unmodifiable
var i: Int = 0 // var means modifiable

fun hello() {
val str = “Hello”
print(“$str World”)
}

fun sum(x: Int, y: Int): Int {
return x + y
}

fun maxOf(a: Float, b: Float) = if (a > b) a else b

}

۲- قالب بندی آسان رشته‌ها

معادل ()String.format در زبان کاتلین بسیار ساده‌تر و هوشمندانه‌تر شده است. به کد پایین نگاهی بیندازید، ببینید چقدر راحت مقادیر متغیر چاپ شده است.

val x = 4
val y = 7
print(“sum of $x and $y is ${x + y}”) // sum of 4 and 7 is 11

۳- درک خودکار انواع

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

val a = “abc” // type inferred to String
val b = 4 // type inferred to Int

val c: Double = 0.7 // type declared explicitly
val d: List<String> = ArrayList() // type declared explicitly

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

۴- کستینگ هوشمند

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

بخوانید  آموزش زبان کاتلین – درس 11 (حلقۀ for)

if (obj is String) {
print(obj.toUpperCase()) // obj is now known to be a String
}

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

۵- تساوی قابل درک

دیگر نیازی به فراخوانی صریح ()equals نیست چرا که اکنون با عملگر == سنجش برابری ساختاری نیز صورت می‌گیرد:

val john1 = Person("John")
val john2 = Person("John")
john1 == john2 // true (structural equality)
john1 === john2 // false (referential equality)

۶- آرگومان‌های پیش‌فرض

نیازی نیست چندین متد با آرگومان‌های متفاوت تعریف کنید، به کمک آرگومان‌های پیش‌فرض که با علامت = مقداردهی می‌شود دیگر نیازی به تعریف Overloadهای مختلف برای متدها نیست:

fun build(title: String, width: Int = 800, height: Int = 600) {
Frame(title, width, height)
}

۷- آرگومان‌های نامگذاری شده

در کنار آرگومان‌های پیش‌فرض، آرگومان‌های نام‌گذاری شده، هرگونه نیازی به builderها را مرتفع می‌کنند:

build(“PacMan”, 400, 300) // equivalent
build(title = “PacMan”, width = 400, height = 300) // equivalent
build(width = 400, height = 300, title = “PacMan”) // equivalent

۸- عبارت When

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

when (x) { 1 -> print(“x is 1”) 2 -> print(“x is 2”) 3, 4 -> print(“x is 3 or 4”) in 5..10 -> print(“x is 5, 6, 7, 8, 9, or 10”) else -> print(“x is out of range”) }

این کلمه هم به صورت expression و هم statement و با آرگومان یا بدون آن قابل استفاده است:

val res: Boolean = when {
    obj == null -> false
    obj is String -> true
    else -> throw IllegalStateException()
}

۹- ویژگی‌ها (Properties)

get و set‌های سفارشی را می‌توانید بر روی فیلدهای عمومی اعمال کنید. این یعنی خلاصی از شر getters و setters های بی‌معنا.

class Frame {
var width: Int = 800
var height: Int = 600

val pixels: Int
get() = width * height
}

۱۰- ساخت سریع کلاس‌های داده (Data Class)

تقریبا در هر برنامه‌ای که می‌سازیم کلاس‌هایی وجود دارند که تنها هدفشان نگه‌داری داده‌ها و حالت یک موجودیت است. مثلا کلاس User چیزهایی مثل نام کاربر، نام خانوادگی، سن و … را ذخیره می‌کند. این کلاس‌ها معمولا فاقد فانکشن‌ها و متدهای خاصی هستند. به این کلاس‌ها در ادبیات کاتلین، data class گفته می‌شود و از آنجایی که بخش عمده‌ای از کلاس‌ها در همین دسته قرار دارند، کاتلین با ساده‌سازی تعریف آن، کار را برای توسعه‌دهندگان راحت کرده است. به مثال پایین دقت کنید:

data class Person(val name: String,
var email: String,
var age: Int)

val john = Person(“John”, “john@gmail.com”, 112)

همانطور که می‌بینید برخلاف جاوا، نیازی به نوشتن صدها خط کد نیست. کاتلین تمامی متدهای مشترک ()toString(), equals(), hashCode و ()copy را به صورت خودکار برای دیتاکلس‌ها تعریف می‌کند.

۱۱- Overload کردن عملگرها

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

بخوانید  آموزش زبان کاتلین – درس 6 (کامنت‌ها)

data class Vec(val x: Float, val y: Float) {
operator fun plus(v: Vec) = Vec(x + v.x, y + v.y)
}

val v = Vec(2f, 3f) + Vec(4f, 1f)

۱۲- اعلان‌های بدون ساختار

این اعلان‌ها که در کاتلین به Destructuring Declarations معروف هستند مزایای زیادی دارند که بهترین کاربردش پیمایش mapهاست:

for ((key, value) in map) {
print(“Key: $key”)
print(“Value: $value”)
}

۱۳- بازه‌ی مقادیر

این یکی هم نیازی به توضیح ندارد؛ از روی کد خودتان می‌فهمید. هدفش خوانایی و کوتاهی بیشتر کدهاست:

for (i in 1..100) { … }
for (i in 0 until 100) { … }
for (i in 2..10 step 2) { … }
for (i in 10 downTo 1) { … }
if (x in 1..10) { … }

۱۴- اکستنشن متد

سی‌شارپ کارها خوب می‌دانند منظور ما چیست. جاوا در حال حاضر فاقد چنین قابلیت مفیدی است. اینکه به کلاس‌های قدیمی بتوانید متدهای جدیدی اضافه کنید ویژگی جالبی است. مثلا کلاس List را در جاوا یادتان هست؛ در این کلاس متد ()sort برای مرتب‌سازی داده‌ها وجود نداشت و ناچار می‌شدید سراغ این متد را در کلاس‌های دیگری مثل Collections بگیرید. یا وقتی نیاز پیدا می‌کردید که کاراکترهای یک رشته را به حروف بزرگ تبدیل کنید، خودتان باید یک کلاس helper یا کمکی می‌نوشتید و خبری از ()StringUtils.capitalize نبود. اما کاتلین اجازه‌ی چنین کاری را به شما می‌دهد. به راحتی می‌توانید متدهای جدیدی به کلاس‌های قدیمی اضافه کنید و IDE هم در هنگام Code Completion اسمش را در لیست پیشنهادی نشان دهد. از این بهتر چه می‌خواهید!

fun String.replaceSpaces(): String {
return this.replace(‘ ‘, ‘_’)
}

val formatted = str.replaceSpaces()

۱۵- امنیت نوع

جاوا زبانی تقریبا استاتیک است. تقریبا از این جهت که در جاوا متغیری از نوع String تضمین نمی‌کند که به یک رشته ارجاع کند بلکه ممکن است ارجاعش به null باشد. اگرچه ما به این موضوع عادت داریم ولی این مسئله در تضاد با چک کردن انواع به صورت استاتیک و در زمان کامپایل است. همین موضوع باعث شده تا برنامه‌نویسان جاوا همواره کابوسشان خطا یا استثنای NullPointerException یا اصطلاحا NPE باشد. در گوگل یا StackOverFlow جستجو کنید ببینید چقدر با این خطا مواجه هستند. زبان کاتلین با تمایز قائل شدن بین انواع غیرتهی (non-null) و آن‌هایی که nullable هستند این مشکل را حل کرده است. در کاتلین به صورت پیش‌فرض همه‌ی انواع غیر تهی هستند و تنها با اضافه کردن علامت ؟ به نوع nullable تبدیل می‌شوند:

var a: String = “abc”
a = null // compile error

var b: String? = “xyz”
b = null // no problem

مثلا در کد بالا متغیر a چون علامت ؟ ندارد یک نوع غیرتهی است و مقداردهی null باعث خطای کامپایل می‌شود. متغیر b با علامت ؟ به کامپایلر اعلام کرده که ممکن است مقدار من null باشد و مقداردهی null همانطور که می‌بینید مجاز است.  کاتلین شما را وادار می‌کند تا هر زمانی با انواع nullable برخورد کردید این موضوع را چک کنید تا در زمان اجرا با خطای NPE مواجه نشوید:

val x = b.length // compile error: b might be null

مثلا در مثال قبلی b صریحا از نوع nullable انتخاب شده بود، بنابراین هر گونه دسترسی بدون کنترلی به این متغیر غیرمجاز است. اینجا کامپایلر خطا می‌دهد چون متغیر b از نوع nullable است و بدون null checking دسترسی به آن صورت گرفته است. این کار اگرچه ممکن است خسته‌کننده و زیاده‌روی به نظر آید ولی به لطف قالیت‌هایی مثل smart cast که به سادگی انواع nullable را به non-null تبدیل می‌کند خیلی هم روی مخ نیست:

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

if (b == null) return
val x = b.length // no problem

همچنین می‌توانید علامت ؟ را به صورت ایمن فراخوانی کنید تا در هنگام تهی بودن متغیر، به جای صادر کردن استثنای NPE، تهی یا null در نظر گرفته شود.

val x = b?.length // type of x is nullable Int

فراخوانی امن می‌تواند به صورت یک زنجیره از علامت‌های سوال به کار رود تا مجبور نباشیم مدام از if-not-null ها استفاده کنیم. اگر دنبال مقدار پیش‌فرض به جز null هستید می‌توانید از عملگر Elvis یعنی : ? استفاده کنیم.

val name = ship?.captain?.name ?: “unknown”

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

val x = b?.length ?: throw NullPointerException() // same as below
val x = b!!.length // same as above

۱۶- لامبدا اکسپرژن بهتر

در کاتلین، عبارات لامبدا یا Lambdas Expression خیلی خیلی سینتکس تمییزتر و سرراست‌تری دارد.

val sum = { x: Int, y: Int -> x + y } // type: (Int, Int) -> Int
val res = sum(4,7) // res == 11

سینتکس جدید نکات جالبی دارد که بهتر است با دو موردش آشنا شوید:

  1. پرانتزها را در صورتی که لامبدا آخرین یا تنها آرگومان متد باشد می‌توان حذف کرد
  2. در صورتی که نخواهیم لامبدای تک آرگومانی را تعریف کنیم، کاتلین به صورت پیش‌فرض نام it را برای آن برمی‌گزیند

توضیحات بالا به صورت عملی در کد پایین نمایش داده شده است. هر سه خط کارایی یکسانی دارند:

numbers.filter({ x -> x.isPrime() })
numbers.filter { x -> x.isPrime() }
numbers.filter { it.isPrime() }

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

persons
.filter { it.age >= 18 }
.sortedBy { it.name }
.map { it.email }
.forEach { print(it) }

سیستم لامبدا در کاتلین در کنار اکستنشن متد گزینه‌ای ایده‌آل برای ساخت DSL است. کتابخانه‌ی Anko مثال خوبی از DSL و کاربردهای بیان شده به منظور بهبود برنامه‌نویسی اندروید است:

verticalLayout {
padding = dip(30)
editText {
hint = “Name”
textSize = 24f
}
editText {
hint = “Password”
textSize = 24f
}
button(“Login”) {
textSize = 26f
}
}

۱۷- پشتیبانی IDE از زبان کاتلین

برای کار با کاتلین چندین IDE روبروی شماست ولی توصیه‌ی ما استفاده از IntelliJ است (اندروید استودیو نیز بر مبنای همین IDE است)، چرا که به همراه کاتلین عرضه می‌شود. صرفنظر از این موضوع، عقل حکم می‌کند که زبان و محیط ساخته شده توسط یک تیم واحد سازگاری بهتری با یکدیگر خواهند داشت. برای مثال به تصویر پایین نگاه کنید؛ این پیغام زمانی ظاهر شد که کدی به زبان جاوا را از Stackoverflow کپی کردیم. هوشمندی این محیط کار توسعه را برای برنامه‌نویسان اندروید راحت‌تر می‌کند.

پشتیبانی Intellij از کاتلین

سید ایوب کوکبی

نویسنده و مترجم...

0 دیدگاه

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *