آموزش زبان کاتلین (درس ۳ : توابع)

نویسنده : سید ایوب کوکبی ۲۲ آبان ۱۳۹۸
آموزش زبان کاتلین (درس 3 : توابع)

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

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

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

val set = setOf(1, 7, 53)

همچنین ساخت map:

val list = listOf(1, 7, 53)
val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

to در اینجا یک تابع است که در ادامۀ این درس در موردش توضیح خواهیم داد. به نظر شما آبجکت‌های ساخته شده (set, list, map) از چه کلاسی هستند؟ کد زیر را اجرا کنید تا متوجه شوید:

>>> println(set.javaClass)
class java.util.HashSet
>>> println(list.javaClass)
class java.util.ArrayList
>>> println(map.javaClass)
class java.util.HashMap

JavaClass در کاتلین معادل ()getClass در جاواست. همانطور که می‌بینید، کاتلین از کلاس‌های استاندارد جاوا استفاده می‌کند. کاتلین همچنین از کلاس‌های اختصاصی برای ساخت کالکشن استفاده نمی‌کند. این یعنی هر چه از جاوا بلدید اینجا هم می‌توانید استفاده کنید.

اما چرا کاتلین کلاس‌های اختصاصی برای ساخت کالکشن ندارد؟ چون استفاده از کلاس‌های استاندارد جاوا، تعامل با کدهای جاوا را آسان‌تر می‌کند. اینطور دیگر نیازی به تبدیل کالکشن‌ها از یک زبان به زبان دیگر نیست. اگرچه کالکشن‌های کاتلین دقیقاً مشابه جاواست ولی در کاتلین می‌توانید کارهای بیشتری با آن انجام داد که به لطف Extension Functionها است. در این مورد در همین درس توضیح می‌دهیم. به عنوان مثال می‌توانید آخرین عنصر از لیست را دریافت کرده یا بیشترین مقدارِ یک کالکشن را پیدا کنید:

>>> val strings = listOf("first", "second", "fourteenth")
>>> println(strings.last())
fourteenth
>>> val numbers = setOf(1, 14, 2)
>>> println(numbers.max())
۱۴

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

فراخوانیِ آسان‌ترِ توابع

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

>>> val list = listOf(1, 2, 3)
>>> println(list)
[۱, ۲, ۳]

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

fun <T> joinToString(
    collection: Collection<T>,
    separator: String,
    prefix: String,
    postfix: String
): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

این تابع از نوع generic بوده؛ بنابراین می‌تواند هر کالکشنی از هر نوعی دریافت کند. سینتکس جنریک در کاتلین همانند جاواست. در مورد جزئیاتش در درس‌های بعد توضیح می‌دهیم. حالا اگر کد را اجرا کنید، نتیجه به همان صورتی است که می‌خواهید:

>>> val list = listOf(1, 2, 3)
>>> println(joinToString(list, "; ", "(", ")"))
(۱; ۲; ۳)

همه چیز درست است فقط کمی تابع طولانی است. شاید نیاز نباشد با هربار فراخوانی ۴ آرگومان ارسال کرد. اینجا می‌توانیم از آرگومان‌های نام‌گذاری شده (Named Arguments) استفاده کنیم.

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

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

joinToString(collection, " ", " ", ".")

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

همین مشکل هنگام استفاده از مقادیر Boolean نیز وجود دارد. در برنامه‌نویسی جاوا معمولاً برای حل این مشکل توصیه شده تا از enum استفاده کنیم یا حتی نام پارامترها را در کامنتی قبل از تابع بنویسیم:

/* Java */
joinToString(collection, /* separator */ " ", /* prefix */ " ",
/* postfix */ ".");

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

joinToString(collection, separator = " ", prefix = " ", postfix = ".")

فقط به این نکته توجه کنید که اگر نام یک پارامتر را استفاده کردید، نام بقیه را هم باید بنویسید؛ در غیر این صورت IDE به شما اجازه نمی‌دهد.

نکته ۱: در IDEA تغییرِ نام پارامترها به صورت خودکار منعکس می‌شود مشروط بر اینکه به جای تغییر دستی از Refactor > Rename استفاده کنید.

نکته ۲: متأسفانه هنگام فراخوانی توابع جاوا (از JDK یا فریم‌ورک اندروید) نمی‌توانید از آرگومان‌های نامگذاری شده استفاده کنید. ذخیرۀ نام پارامترها در فایل‌های class. به عنوان یک فیچر اختیاری از جاوا ۸ به بعد شروع شد. این در حالی است که کاتلین سازگار با جاوا ۶ طراحی شده است. آرگومان‌های نام‌گذاری شده به خوبی با پارامترهای پیش‌فرض کار می‌کنند که در ادامه توضیح می‌دهیم.

مقادیر پیش‌فرض برای پارامترها

مشکل رایج بعدی در جاوا، زیاد شدن تعداد متدهای overload در برخی از کلاس‌هاست. مثلا کلاس java.lang.Thread دارای ۸ سازنده است. اورلود به دلایل متعددی از جمله سازگاری با نسخه‌های پیشین، راحتیِ استفاده از API و … استفاده می‌شود ولی این قابلیت باعث تکرار در کدها می‌شود. نام پارامترها و انواع، بارها و بارها تکرار می‌شود. همچنین در هنگام استفاده از یک اورلود که چند پارامترِ آن نادیده گرفته شده، دقیقاً معلوم نیست که کدام مقدار برای کدام پارامتر است.

در کاتلین با استفاده از مقادیر پیش‌فرض برای پارامترها، می‌توانید جلوی اورلود شدن توابع را بگیرید. بیایید تابع قبلی‌مان joinToString را با استفاده از این قابلیت ساده‌‌تر کنیم. در اغلب موارد برای چاپِ یک لیست نیازی به درج علامت در ابتدا و انتهای لیست نیست و عناصر در حالت معمول با ویرگول از هم جدا می‌شوند. حال می‌توانیم این سه مورد را به صورت پیش‌فرض برای پارامترهای تابع فوق تعریف کنیم:

fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String

اکنون می‌توانید تابع را بدون درج پارامترها فراخوانی کنید:

>>> joinToString(list, ", ", "", "")
۱, ۲, ۳
>>> joinToString(list)
۱, ۲, ۳
>>> joinToString(list, "; ")
۱; ۲; ۳

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

>>> joinToString(list, prefix = "# ")
# ۱, ۲, ۳

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

/* Java */
String joinToString(Collection<T> collection, String separator,
String prefix, String postfix);
String joinToString(Collection<T> collection, String separator,
String prefix);
String joinToString(Collection<T> collection, String separator);
String joinToString(Collection<T> collection);

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

توابع و پراپرتی‌های Top-Level

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

نتیجه این می‌شود که کلاس‌هایی می‌سازید که حاوی هیچ stateای نیستند، هیچگونه instance متدی ندارند و در واقع کلاسی دارید که فقط حاوی متدهای استاتیک است. مثال واضحش کلاس Collections در JDK. در کدهای شما یا کدهای دیگران احتمالاً کلاس‌های فراوانی با پسوند Util وجود دارد. این کلاس‌ها همان‌هایی هستند که باعث شلوغ شدن بی‌دلیل پروژه می‌شوند.

بخوانید  MVP در اندروید با یک مثال ساده

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

تابع joinToString را مستقیماً در پکیج strings تعریف می‌کنیم. فایلی به نام join.kt با محتوای زیر ایجاد کنید:

package strings
fun joinToString(...): String { ... }

از آنجایی که JVM فقط کدهای درون کلاس‌ها را اجرا می‌کند، بعد از کامپایل شدن این فایل، تعدادی کلاس تولید می‌شود. در کاتلین به چیز دیگری نیاز ندارید ولی در جاوا اگر بخواهید این تابع را صدا بزنید باید نام کلاسی که در آن قرار گرفته را هم بدانید. کامپایلر از نامِ فایل join.kt استفاده کرده و کلاسی به اسم JoinKt می‌سازد. درون این فایل، تابعی تعریف شده به صورت استاتیک قرار می‌گیرد:

/* Java */

package strings;

public class JoinKt {

public static String joinToString(...) { ... }

}

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

/* Java */
import strings.JoinKt;
...
JoinKt.joinToString(list, ", ", "", "");

برای عوض کردن نام کلاس، در ابتدای فایل و پیش از نام پکیج، عبارت @JvmName را قرار دهید و نامی که می‌خواهید برای کلاس انتخاب شود را درون پرانتز بنویسید:

@file:JvmName("StringFunctions")
package strings
fun joinToString(...): String { ... }

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

/* Java */
import strings.StringFunctions;
StringFunctions.joinToString(list, ", ", "", "");

در مورد جزئیات این کار در درس‌های بعد توضیح خواهیم داد.

همانند توابع، پراپرتی‌ها را هم می‌توان به صورت Top-Level تعریف کرد. ذخیرۀ داده‌ها خارج از کلاس، ایدۀ خوبی نیست ولی گاهی مفید است. برای مثال با تعریف یک پراپرتی می‌توانید تعداد دفعات اجرای یک عمل را ذخیره کنید:

var opCount = 0
fun performOperation()
{ opCount++
// ...
}
fun reportOperationCount() {
println("Operation performed $opCount times")
}

پراپرتی opCount به صورت استاتیک ذخیره می‌شود. چنین پراپرتی‌هایی برای تعریف ثوابت نیز به کار می‌روند:

val UNIX_LINE_SEPARATOR = "\n"

در حالت پیش‌فرض، پراپرتی‌های Top-Level همانند سایر پراپرتی‌ها در جاوا به صورت متدهای accessor نوشته می‌شوند (getter و setter.) در صورتی که بخواهید ثابت را به صورت یک فیلد public static final تعریف کنید باید از مودیفایر const استفاده کنید. این مودیفایر برای پراپرتی‌هایی از انواع primitive مثل String قابل استفاده هستند:

const val UNIX_LINE_SEPARATOR = "\n"

که به کد زیر در جاوا کامپایل می‌شود:

/* Java */
public static final String UNIX_LINE_SEPARATOR = "\n"

تابع کمکی joinToString را به اندازۀ زیادی بهبود دادیم. اکنون زمان آن رسیده تا کمی استفاده از آن را کاربردی‌تر کنیم.

Extension Function

 یکی از مهم‌ترین ویژگی‌های زبان کاتلین، سهولت در ترکیب با کدهای موجود است. حتی پروژه‌های تماماً کاتلین بر روی کتابخانه‌های جاوا مثل JDK یا فریم‌ورک‌هایی مثل Android SDK بنا شده‌اند. وقتی کاتلین را در کنار زبان جاوا استفاده می‌کنید با کدهایی روبرو می‌شوید که نباید یا نمی‌شود به کاتلین تبدیل کرد. اما این جا هم راهی وجود دارد: استفاده از extension function ها.

یک extension function به زبان ساده، تابعی است که می‌تواند به عنوان عضوی از یک کلاس فراخوانی شود بدون اینکه واقعاً عضوی از آن باشد! (در واقع خارج از آن تعریف شده ولی عضوی از آن است!) به عنوان مثال، متدی تعریف می‌کنیم که آخرین کاراکتر یک رشته را به ما بدهد:

package strings
fun String.lastChar(): Char = this.get(this.length - 1)

همانطور که می‌بینید، تمام چیزی که برای این کار نیاز دارید. نوشتن نام کلاس یا اینترفیسی است که می‌خواهید گسترش دهید و بعد از آن نام تابعی که می‌خواهید عضوی از آن کلاس باشد. به کلاس فراخوانی شده receiver type می‌گویند و به مقداری که روی آن کلاس فراخوانی شده receiver object.

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

>>> println("Kotlin".lastChar())
n

در این مثال، String در واقع receiver type و Kotlin همان receiver object است. به بیان دیگر، شما بدون دسترسی به سورس کلاس String و بدون اینکه وارد آن کلاس شوید، متد جدیدی را به آن اضافه کردید یا به عبارتی آن را extend کردید. مهم نیست String در جاوا، کاتلین یا سایر زبان‌های JVM مثل Groovy نوشته شده. مادامی که به کدهای جاوا کامپایل شود می‌توانید برایشان extension function اضافه کنید. در بدنۀ این توابع از کلمۀ this استفاده می‌‌شود که البته می‌توانید آن را حذف کنید چون کاتلین به صورت ضمنی فراخوانی می‌کند:

package strings
fun String.lastChar(): Char = get(length - 1)

در این توابع ، مستقیماً می‌توانید به متدها و پراپرتی‌های کلاس، همانند اعضای آن کلاس دسترسی داشته باشید. البته extension function ها، قواعد encapsulation را نقض نمی‌کنند و امکان دسترسی به اعضای private یا protected را به شما نمی‌دهند.

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

import strings.lastChar
val c = "Kotlin".lastChar()

همچنین از * می‌توانید استفاده کنید:

import strings.*
val c = "Kotlin".lastChar()

در هنگام ایمپورت کردن، می‌توانید نام تابع ایمپورت شده را به چیز دیگری تغییر دهید:

import strings.lastChar as last
val c = "Kotlin".last()

این تغییر نام، زمانی مفید است که چند فانکشن با نام‌های مشابه در پکیج‌های مختلف تعریف کرده‌اید و حال می‌خواهید در یک فایل از همۀ آن‌ها استفاده کنید. برای کلاس‌ها یا فانکشن‌های معمولی می‌توانید ازfully qualified name استفاده کنید ولی برای توابع اکستند شده، تنها با همین روش یعنی استفاده از as و تعریف یک نام جدید می‌توانید تداخل را از بین ببرید.

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

فراخوانی extension function ها بدون آبجکت های واسط یا هر نوع سربار اجرایی دیگری صورت می‌گیرد. تابع اکستند شده در واقع یک متد استاتیک است که یک receiver object به عنوان اولین آرگومان می‌پذیرد. این باعث می‌شود فراخوانی این توابع در جاوا آسان باشد. کافی است متد استاتیک را فراخوانی کنید و receiver object را به عنوان آرگومان به آن بدهید. همانند فانکشن‌های سطح بالا، نام کلاس دربردارندۀ این تابع، از روی فایلی که در آن تعریف شده استخراج می‌شود. مثلاً اگر فایل ما اسمش StringUtil.kt بود، فراخوانی تابع به این صورت انجام می‌شود:

/* Java */
char c = StringUtilKt.lastChar("Java")

این extension function به عنوان یک تابع سطح بالا در قالب یک متد استاتیک کامپایل می‌شود. شما می‌توانید متد lastChar را به صورت استاتیک در جاوا فراخوانی کنید. کد بالا اگرچه از معادل کاتلین‌اش خوانایی کمتری دارد ولی از این نقطه نظر که همکاری دو زبان را نشان می‌دهد جالب است.

توابع کمکی به عنوان extension

اکنون می‌توانید نسخۀ نهایی joinToString را بنویسید. این تقریباً همان چیزی است که به صورت استاندارد در کتابخانۀ استاندارد کاتلین تعریف شده است:

fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex())
{ if (index > 0)
result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}

همانطور که می‌بینید تابع را به extension function تبدیل کرده‌ایم و برای آرگومان‌ها مقادیر پیش‌فرض تعریف کرده‌ایم. حال می‌توانید این تابع را همچون عضوی از یک کلاس روی list فراخوانی کنید:

>>> val list = arrayListOf(1, 2, 3)
>>> println(list.joinToString(" "))
۱ ۲ ۳

امکان override کردن extension function ها وجود ندارد

Override کردن متدها در کاتلین برای ممبر فانکشن‌ها امکان‌پذیر بود ولی برای توابع اکستند شده مقدور نیست. کلاس View و زیرکلاسش Button را در نظر بگیرید. کلاس Button تابع click کلاس مادر را override می‌کند:

open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
}

شما می‌توانید مقداری از نوع Button را درون متغیری از نوع View ذخیره کنید. دلیلش این است که Button زیرکلاسی از View است. در این حالت اگر یک متد معمولی مثل click را روی این متغیر فرخوانی کنید و آن متد در کلاس Button رونویسی (override) شده باشد، کدهای نسخۀ override شده اجرا می‌شود:

>>> val view: View = Button()
>>> view.click()
Button clicked

ولی همانطور که در تصویر می‌بینید، این کار برای extension function ها امکان‌پذیر نیست.

توابع  اکستند شده، جزئی از کلاس نبوده و در خارج از آن تعریف می‌شوند. در مثال پایین تابع اکستنشن showoff را می‌بینید که در کلاس‌های View و Button تعریف شده است:

fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
>>> val view: View = Button()
>>> view.showOff()
I'm a view!

وقتی شما تابع showoff را بر روی متغیری از نوع View صدا می‌زنید، اکستنشن نظیرش فراخوانی می‌شود حتی اگر نوع متغیر Button باشد. اگر این کار را با نسخۀ کامپایل شدۀ جاوا انجام دهید راحت‌تر می‌توانید موضوع را درک کنید:

/* Java */
>>> View view = new Button();
>>> ExtensionsKt.showOff(view);
I'm a view!

همانطور که می‌بینید، overriding روی extension function ها انجام نشده و کاتلین آن‌ها را به عنوان توابعی استاتیک در نظر گرفته است. در صورتی که یک کلاس دارای توابع عضو با signature مشابهی با توابع extension باشد، توابع عضو همیشه در اولویت قرار می‌گیرند. بنابراین هنگام توسعۀ API همیشه به این موضوع توجه کنید. در مورد متدها به اندازۀ کافی صحبت کردیم، اکنون بپردازیم به Extension property ها.

Extension Properties

extension property ها همانند extension function ها هستند، با این تفاوت که به جای تابع از پراپرتی استفاده می‌شود و اگرچه به آن‌ها پراپرتی گفته می‌شود ولی نمی‌توانند state ای داشته باشند. امکان اضافه کردن فیلدهای جدید به آبجکت‌های جاوا وجود ندارد ولی گاهی سینتکس کوتاه‌تر مفیدتر است. بالاتر دیدید که چطور از تابع lastChar استفاده کردیم. حال بیایید این تابع را به یک پراپرتی تبدیل کنیم:

val String.lastChar: Char
get() = get(length - 1)

همانطور که می‌بینید اینجا نیز مثل توابع، با تعریف رایج پراپرتی سروکار داریم. قسمت get همیشه باید تعریف شود؛ چون هیچ بکینگ فیلدی وجود ندارد و بنابراین هیچ getter پیش‌فرضی هم وجود ندارد. initialize کردن پراپرتی نیز مقدور نیست. علتش این است که جایی برای ذخیرۀ مقدار آن وجود ندارد چون خارج از کلاس تعریف شده و کارکردش مانند یک پراپرتی که در کلاس تعریف شده نیست.

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

اگر همین پراپرتی را در کلاس StringBuilder تعریف می‌کردید می‌توانستید از var استفاده کنید چون محتوای StringBuilder را می‌توان تغییر داد:

var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
this.setCharAt(length - 1, value)
}

دسترسی به extension property ها دقیقاً مانند پراپرتی‌های معمولی است:

>>> println("Kotlin".lastChar)
n
>>> val sb = StringBuilder("Kotlin?")
>>> sb.lastChar = '!'
>>> println(sb)
Kotlin!

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

StringUtilKt.getLastChar("Java"

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

varargs, infix calls

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

این درس را با این توضیح شروع کردیم که کالکشن‌ها در کاتلین همان کلاس‌های رایجِ جاوا هستند که extend شده‌اند. جلوتر مثال‌هایی بیان کردیم که آخرین آیتم یک لیست را برمی‌گرداند و بیشترین مقدار یک کالکشن عددی را نشان می‌داد:

>>> val strings: List<String> = listOf("first", "second", "fourteenth")
>>> strings.last()
fourteenth
>>> val numbers: Collection<Int> = setOf(1, 14, 2)
>>> numbers.max()
۱۴

علاقه‌مند بودید که بدانید این ویژگی چطور کار می‌کند. چطور است که این همه کار می‌توان با کالکشن‌های کاتلین انجام داد در حالی که از همان کلاس‌های مرسوم جاوا استفاده می‌کنیم. احتمالاً باید جواب سوالتان را گرفته باشید. last و max توابعی از نوع extension function بودند. آن‌ها خارج از کلاس تعریف شده‌اند و این همان کاری است که در کتابخانۀ استاندارد کاتلین صورت گرفته است. کاتلین در این کتابخانه مجموعه‌ای از توابع جدید را به توابع قبلی جاوا اضافه کرده است.

تابع last پیچیده‌تر از تابع lastChar برای کلاس String نیست. این فقط یک گسترش ساده است که روی کلاس‌های قبلی اتفاق افتاده است. تابع max در کتابخانۀ کاتلین شبیه همان تابع max ای است که ما تعریف کردیم منتهی با این تفاوت که بزرگترین مقدار را نه‌فقط برای Int بلکه برای هر نوعی که comparable هست پیدا می‌کند:

fun <T> List<T>.last(): T { /* returns the last element */ }
fun Collection<Int>.max(): Int { /* finding a maximum in a collection */ }

بسیاری از extension function ها در کتابخانۀ استاندارد کاتلین تعریف شده‌اند که ما اینجا نمی‌خواهیم همه‌شان را معرفی کنیم. شاید برایتان سوال باشد چطور می‌توانیم این همه تابع را یاد بگیریم. پاسخ این است که نیازی نیست همه چیز را از قبل بدانید. در IDE هر زمان که لازم باشد، توابع مختلف هنگام auto completion نمایش داده می‌شود. این لیست هم حاوی متدهای معمولی و هم فانکشن‌های توسعه یافته است.

در ابتدای این درس دیدید که تابع listOf برای ساخت کالکشنی از اعداد تعداد بی‌شمار آرگومان می‌پذیرد. اما چطور؟ پاسخ در ادامه…

varargs: توابعی با بی‌شمار آرگومان

در تابع listOf هر تعداد آرگومان که بخواهید ارسال می‌کنید:

val list = listOf(2, 3, 5, 7, 11)

در صورتی که پیاده‌سازی این تابع را ببینید، چنین چیزی می‎بینید:

fun listOf<T>(vararg values: T): List<T> { ... }

شما احتمالاً با varargs در جاوا آشنا هستید؛ قابلیتی که به شما اجازه می‌دهد تا بی‌نهایت آرگومان به یک متد بفرستید. همۀ آرگومان‌ها به صورت یک آرایه به متد مقصد ارسال می‌شوند. این ویژگی در کاتلین هم وجود دارد منتهی با کمی تفاوت. به جای سه نقطه اینجا از کلمۀ varargs پیش از نام پارامتر استفاده می‌شود.

تفاوت دیگر هنگامی است که آرگومان‌ها از قبل به صورت آرایه است. در جاوا آرایه را به همان صورتی که هست به پارامتر varargs ارسال می‌کنید؛ اما در کاتلین آرایه را قبل از ارسال حتماً باید unpack کنید. برای اینکار لازم است تا پیش از نام آرایه یک * قرار دهید. این عملگر اصطلاحاً spread operator نامیده می‌شود که در زبان کاتلین با علامت * مشخص شده و در زبان‌های دیگری مثل جاوا اسکریپت با سه نقطه مشخص می‌شود:

fun main(args: Array<String>) {
val list = listOf("args: ", *args)
println(list)
}

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

فراخوانی به صورت infix

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

val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

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

۱٫to("one")
۱ to "one"

فراخوانی infix روی توابع معمولی و extension function ها کار نمی‌کند مگر پیش از تعریفشان از مودیفایر infix استفاده کنید. در اینجا نسخۀ ساده شده‌ای از تابع to را می‌بینید:

infix fun Any.to(other: Any) = Pair(this, other)

تابع to، نمونه (instance)ای از Pair را برمی‌گرداند که یکی از کلاس‌های کتابخانۀ استاندارد کاتلین است. این کلاس یک زوج از مقادیر را تعریف می‌کند. در پیاده‌سازی واقعی Pair و to از جنریک‌ها استفاده شده که در اینجا برای حفظ سادگی و درک بهتر، حذف شده‌اند. شما می‌توانید یک جفت از مقادیر را به صورت مستقیم به دو متغیر نسبت دهید:

val (number, name) = 1 to "one"

به این ویژگی destructuring declarion گفته می‌شود که در شکل پایین عملکرد آن را مشاهده می‌کنید:

این ویژگی مختص pair نیست. شما می‌توانید یک map را به دو متغیر مجزا اختصاص دهید. در حلقه‌ها نیز کاربرد دارد که مثالش را در پیاده‌سازی joinToString که از withIndex استفاده کردیم می‌توانید ببینید:

for ((index, element) in collection.withIndex())
{ println("$index: $element")
}

تابع to یک extension function است که با استفاده از آن می‌توانید یک جفت از هر چیزی بسازید. مثلاً:

۱ to "one"
"one" to 1
list to list.size()
...

نگاهی به تعریف تابع mapOf بیندازیم:

fun <K, V> mapOf(vararg values: Pair<K, V>): Map<K, V>

همانند listOf در mapOf نیز از varargs برای دریافت آرگومان‌ها استفاده می‌شود، ولی این بار نوع آرگومان‌ها، یک جفت key/value است که در قالب یک Pair ارسال می‌شود. اگرچه ساخت map یک ساختار ویژه را در ذهن ما ترسیم می‌کند ولی در حقیقت یک تابع معمولی است که سینتکس آن کوتاه‌تر شده است.

در ادامه خواهید دید extension ها چگونه کار با رشته‌ها و Regular Expression را آسان می‌کنند.

کار با رشته‌ها و رگولار اکسپرشن

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

تقسیم (split) کردن رشته‌ها

احتمالاً با متد split از کلاس String آشنا هستید. خیلی‌ها از این متد استفاده می‌کنند ولی خیلی از افراد هم در Stack Overflow در مورد این متد ایراداتی بیان کرده‌اند. Split در جاوا با نقطه مشکل دارد. مثلاً برای این کد خروجی زیر را انتظار داریم:

۱۲٫۳۴۵-۶٫A".split(".")
//Expected result: [12, 345-6, A]

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

به عنوان مثال اورلودی که یک رگولار اکسپرشن به عنوان آرگومان قبول می‌کند حتماً باید مقداری از نوع Regex داشته باشد نه String. این به ما کمک می‌کند تا مطمئن شویم که رشتۀ ارسالی به متد به عنوان یک رشته تفسیر می‌شود یا رگولار اکسپرشن. در اینجا رشتۀ بالا را بر اساس نقطه یا خط تیره تجزیه می‌کنیم:

>>> println("12.345-6.A".split("\\.|-".toRegex()))
[۱۲, ۳۴۵, ۶, A]

کاتلین دقیقاً از سینتکس مشابه رگولار اسکپرشن در جاوا استفاده می‌کند. API ای که برای کار با عبارات باقاعده در نظر گرفته شده نیز مانند API استاندارد کتابخانۀ جاوا است ولی با اسامی بهتر. برای مثال در کاتلین برای تبدیل یک رشته به رگولار اسکپرشن از اکستنشن فانکشنِ toRegex استفاده می‌شود.

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

println("12.345-6.A".split(".", "-"))
[۱۲, ۳۴۵, ۶, A]

همچنین می‌توانید از کاراکتر هم به عنوان جداکننده استفاده کنید. یعنی به جای رشته از علامت single quote استفاده کنید؛ مثلاً ‘.’

بخوانید  درس‌هایی از 21 برنامه‌ی اندرویدی

این متد، اورلود جاوا که فقط یک کاراکتر را به عنوان ورودی می‌پذیرد مخفی می‌کند.

استفاده از سه علامت “

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

fun parsePath(path: String) {
val directory = path.substringBeforeLast("/")
val fullName = path.substringAfterLast("/")
val fileName = fullName.substringBeforeLast(".")
val extension = fullName.substringAfterLast(".")
println("Dir: $directory, name: $fileName, ext: $extension")
}
>>> parsePath("/Users/yole/kotlin-book/chapter.adoc")
Dir: /Users/yole/kotlin-book, name: chapter, ext: adoc

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

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

fun parsePathRegexp(path: String) {
val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (matchResult != null) {
val (directory, filename, extension) = matchResult.destructured
println("Dir: $directory, name: $filename, ext: $extension")
}
}

در این تابع عبارت باقاعده درون یک tripple quoted (“””) نوشته شده است. در چنین رشته‌ای دیگر نیاز به بک اسلش به عنوان escape character نداریم؛ بنابراین می‌توانید مستقیماً از .\ به جای .\\ درون رشته استفاده کنید.

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

حال اجازه دهید به توضیح تابع parsePathRegexp بپردازیم. ابتدا یک رگولار اکسپرشن ایجاد شده و روی ورودی اعمال می‌شود. اگر نتیجه‌ای مچ شد – یعنی خروجی null نبود – مقدار پراپرتی destructured آن به متغیری اختصاص داده می‌شود. در مورد این سنتکس پیش‌تر توضیح دادیم.

کاربرد “”” برای رشته‌های چند سطری

هدف از رشته‌هایی که درون “”” قرار می‌گیرند فقط escape کردن کاراکترها نیست. این رشته‌ها می‌توانند حاوی هر کاراکتری از جمله line break نیز باشند. بنابراین راه آسانی برای نوشتن line break در اختیار شما قرار می‌دهد. مثلاً یک عبارت اسکی:

val kotlinLogo = """| //
.|//
.|/ \"""
>>> println(kotlinLogo.trimMargin("."))
| //
|//
|/ \

رشته‌های چند سطری تمام کاراکترها از جمله line break هایی که با اینتر زدن به وجود آورده‌اید را شامل می‌شوند. بنابراین در خروجی هم به همین صورت نمایش داده می‌شود. تورفتگی یا هر کاراکتری که در این رشته‌ها قرار دهید در خروجی نمایش داده می‌شود. البته در این رشته‌ها حق استفاده از special chracter هایی مثل n\ را ندارید. به عبارت دیگر، شما نباید از کاراکتر escape sequence استفاده کنید. یعنی مسیر “C:\Users\yole\kotlin-book” باید به صورت “””C:\Users\yole\kotlin-book””” نوشته شود.

یکی از کاربردهای این رشته‌ها (به جز ASCII Art) استفاده از آن‌ها در تست است. احتمالاً بارها برایتان پیش آمده که برنامۀ شما یک متن چند سطری را به عنوان خروجی برمی‌گرداند (مثلاً یک صفحۀ وب) و شما این متن را با خروجی مورد انتظارتان مقایسه می‌کنید. رشته‌های چند سطری راهی عالی برای ذخیره کردن خروجی مورد انتظار هستند. دیگر لازم نیست با کاراکترهای escape sequence رشته را شلوغ کنید و با فراموش کردن یکی از این کاراکترها نتیجۀ تست را برخلاف انتظارتان True یا False ببینید. فقط سه علامت “”” می‌گذارید و هر رشته‌ای که دوست دارید را درون آن ذخیره می‌کنید.

همانطور که می‌بینید بخش عمده‌ای از کتابخانۀ استاندارد کاتلین به توسعۀ extension function ها روی کلاس‌های جاوا اختصاص یافته است. کتابخانۀ Anko نیز که به صورت Built-in توسط JetBrains ساخته شده همین کار را روی Android SDK انجام می‌دهد تا کدهای اندروید حال و هوای کاتلین به خود بگیرند. برای کتابخانه‌های دیگری چون Spring و … نیز wrapper های کاتلین وجود دارند که با جستجو در اینترنت می‌توانید پیدا کنید.

خب بعد از معرفی extension function ها و قابلیت‌های دیگر کاتلین کمی هم در مورد local function ها صحبت کنیم.

کدهای تمیزتر با local function

بسیاری از توسعه‌دهندگان یکی از فاکتورهای باکیفیت بودن کد را در نبود کدهای تکراری می‌دانند که اصطلاحاً به آن قاعدۀ DRY (سرنام Don’t Repeat Yourself) گفته می‌شود. ولی در کدهای جاوا این اصل خیلی جدی گرفته نمی‌شود.

در بسیاری از موارد با امکانات IDE می‌توانید متدهای طولانی را به متدهای کوچکتر بشکنید ولی این کار می‌تواند خوانایی کد را پایین آورد چون توابع کوچکی به وجود آمده که رابطۀ بین آن‌ها روشن نیست. شاید بتوانید این متدها را در گروه‌های مرتبط درون کلاس‌ها قرار دهید ولی این موضوع هم به طولانی شدن فراخوانی متدها منجر خواهد شد. پس چاره چیست؟

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

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

class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
if (user.name.isEmpty()) {
throw IllegalArgumentException(
"Cannot save user ${user.id}: Name is empty")
}
if (user.address.isEmpty())
{ throw
IllegalArgumentException(
"Cannot save user ${user.id}: Address is empty")
}
// Save user to the database
}
>>> saveUser(User(1, "", ""))
java.lang.IllegalArgumentException: Cannot save user 1: Name is empty

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

class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(user: User,
value: String,
fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException(
"Cannot save user ${user.id}: $fieldName is empty")
}
}
validate(user, user.name, "Name")
validate(user, user.address, "Address")
// Save user to the database
}

قسمتی مربوط به ارزیابی داده‌ها را در یک متد محلی نوشتیم و به تعداد فیلدهای مختلف می‌توانیم این متد را فراخوانی کنیم. ولی ارسال شی User تکراری است و به نظر تکراری می‌آید. خبر خوب اینکه نیازی به ارسال شی نیست. توابع محلی چون داخل تابع تعریف شده‌اند به تمام متغیرهای آن دسترسی دارند. پس با علم به این موضوع پارامتر User را از تابع محلی حذف می‌کنیم:

class User(val id: Int, val name: String, val address: String)
fun saveUser(user: User) {
fun validate(value: String, fieldName: String)
{ if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user ${user.id}: " +
"$fieldName is empty")
}
}
validate(user.name, "Name")
validate(user.address, "Address")
// Save user to the database
}
>>> saveUser(User(1, "", ""))
java.lang.IllegalArgumentException: Cannot save user 1: Name is empty

برای بهبود بیشتر می‌توانید قسمت اعتبارسنجی را در یک extension function در کلاس User بنویسید.

class User(val id: Int, val name: String, val address: String)
fun User.validateBeforeSave() {
fun validate(value: String, fieldName: String)
{ if (value.isEmpty()) {
throw IllegalArgumentException(
"Can't save user $id: empty $fieldName")
}
}
validate(name, "Name")
validate(address, "Address")
}
fun saveUser(user: User)
{ user.validateBeforeSave
()
// Save user to the database
}

استخراج قطعه‌ای از کد و تزریق آن در یک extension function بسیار مفید است. این توابع را حتی می‌توان به صورت توابع لوکال تعریف کرد. بنابراین شما می‌توانید کد بالا را با قرار دادن متد User.validateBeforeSave در یک تابع لوکال دیگر به اسم saveUser کدها را خواناتر کنید. البته تودرتو کردن زیادِ توابع لوکال باعث افت خوانایی می‌شود. بنابراین همیشه باید جانب اعتدال را رعایت کنید. در حالت کلی بهتر است میزان تودرتو کردن توابع لوکال بیشتر از یک لایه نباشد. در این فصل ویژگی‌های زیادی از کاتلین معرفی کردیم. در درس بعدی به موضع کلاس‌ها و مفاهیم مرتبط با آن خواهیم پرداخت.

خلاصه:

  • کاتلین از API اختصاصی استفاده نمی‌کند بلکه API جاوا را گسترش داده است؛
  • تعریف مقادیر پیش‌فرض برای پارامترهای یک تابع، تعداد اورلودهای آن تابع را به صورت محسوسی کاهش می‌دهد و همچنین آرگومان‌های نامگذاری شده، فراخوانی توابع را آسان‌تر و خواناتر می‌کنند؛
  • در کاتلین توابع و پراپرتی‌ها را به صورت مستقیم می‌توانید درون فایل تعریف کنید. نیازی نیست تابع یا پراپرتی را به عنوان عضوی از کلاس تعریف کنید. این ویژگی، انعطاف زیادی در نوشتن کدها به شما می‌دهد؛
  • Extension function ها به شما کمک می‌کند تا هر کلاسی اعم از کلاس‌های خودتان، کلاس‌های کتابخانۀ استاندارد کاتلین یا حتی کلاس‌های جاوا و سایر کتابخانه‌های خارجی را بدونِ تغییر سورسشان گسترش دهید؛
  • فراوانی Infix راهی ساده برای فراخوانی متدها با استفاده از یک عملگر فراهم می‌کند؛
  • در کاتلین تعداد زیادی تابع مفید برای مدیریت رشته‌ها، هم برای رشته‌های معمولی و هم Regular Expression وجود دارد؛
  • علامت “”” برای نگه‌داری رشته‌ها به همان صورتی که هستند به کار می‌رود. این رشته‌ها می‌توانند حاوی هر کاراکتری باشند از جمله Line break که هنگام اینتر زدن ایجاد می‌شود؛
  • توابع محلی یا Local Function به شما کمک می‌کند تا ساختار کد را بهبود بخشیده و کدهای تکراری را به حداقل برسانید.

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

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

0 دیدگاه

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