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

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

اعداد

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

کاتلین مقادیر عددی زیر به صورت داخلی دارد (نزدیک به جاوا):

نوع طول (بر اساس بیت)
Double ۶۴
Float ۳۲
Long ۶۴
Int ۳۲
Short ۱۶
Byte ۸

توجه کنید که در کاتلین متغیرهای حرفی یا کاراکترها عدد نیستند.

ثابت‌های لفظی

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

ده‌دهی مثل ۱۲۳

ثابت لفظی از نوع Long با پسوند L تعریف می‌شود مثل ۱۲۳L

شانزدهی مثل ۰x0f

دودویی مثل ۰b00001011

توجه کنید که مقادیر اکتال یا هشتی پشتیبانی نمی‌شوند.

کاتلین برای مقادیر اعشاری هم نمادهای رایج را پشتیبانی می‌کند:

ثابت‌های لفظی اعشاری به صورت پیش‌فرض از نوع Double هستند مثل ۱۲۳٫۵ یا ۱۲۳٫۵e10

مقادیر شناور یا float با برچسب f یا F تعریف می‌شوند مثل ۱۲۳٫۵f

زیرخط یا Underscore در مقادیر لفظی عددی

می‌توانید از زیرخط در مقادیر عددی لفظی استفاده کنید و خوانایی آن را بالا ببرید:

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

نمایش

در پلتفرم جاوا اعداد به صورت انواع اولیه JVM به صورت فیزیکی ذخیره می‌شوند مگر وقتی که یک عدد نول شونده (nullable) بخواهیم (?Int) یا از آن‌ها در جنریک‌ها استفاده کنیم. در حالت دوم اعداد اصطلاحا بسته‌بندی (box) می‌شوند.

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

val a: Int = 10000
print(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!Prints 'false'!!!

به عبارت دیگر بسته‌بندی برابری را حفظ می‌کند:

val a: Int = 10000
print(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // Prints 'true'

تبدیل صریح

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

// Hypothetical code, does not actually compile:
val a: Int? = 1 // A boxed Int (java.lang.Integer)
val b: Long? = a // implicit conversion yields a boxed Long (java.lang.Long)
print(a == b) // Surprise! This prints "false" as Long's equals() check for other part to be Long as well

بنابراین نه فقط یکتایی که برابری هم ممکن است در سکوت از بین برود.

به عنوان نتیجه، انواع کوچکتر به صورت ضمنی به انواع بزرگ‌تر تبدیل نمی‌شوند. یعنی نمی‌توانیم بدون تبدیل نوع صریح مقداری از نوع Byte را به یک متغیر از نوع Int بدهیم:

val b: Byte = 1 // OK, literals are checked statically
val i: Int = b // ERROR

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

val i: Int = b.toInt() // OK: explicitly widened

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

  • toByte() : Byte
  • toShort() : Short
  • toInt() : Int
  • toLong() : Long
  • toFloat() : Float
  • toDouble(): Double
  • toChar() : Char

نبود تبدیل نوع ضمنی به سختی مشهود است چون نوع را از زمینه می‌شود استنتاج کرد و عملیات ریاضی برای تبدیل نوع متناسب «پربار» یا Overload شده‌اند. مثلا:

val l = 1L + 3 // Long + Int => Long

عملیات

کاتلین از مجموعه‌ای از عملیات استاندارد ریاضی بر روی اعدا پشتیبانی می‌کند که به عنوان اعضای کلاس‌های متناظر تعریف شده‌اند (که البته کامپایلر آن‌ها را به دستورالعمل‌های متناظر بهینه‌سازی می‌کند). بخش «پربار کردن عملگرها» را ببینید.

برای عملگرهای بیتی کاراکتر ویژه‌ای وجود ندارد اما توابع با اسمی وجود دارد که می‌توان آن‌ها را به صورت میان‌وند صدا زد. مثلا:

val x = (1 shl 2) and 0x000FF000

این لیست کامل عملگرهای بیتی است (فقط برای Int و Long وجود دارند):

  • shl(bits) – شیفت به چپ با علامت (همان >> جاوا)
  • shr(bits) – شیفت به راست با علامت (همان << جاوا)
  • ushr(bits) – شیفت به راست بدون علامت (همان <<< جاوا)
  • and(bits) – عملگر AND بیتی
  • or(bits) – عملگر OR بیتی
  • xor(bits) – عملگر XOR بیتی
  • inv(bits) – عملگر معکوس کننده بیتی

مقایسه اعداد ممیز شناور

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

  • چک کردن برابری: a == b و a != b
  • عملگرهای مقایسه‌ای: a<b و a>b و a<=b و a>= b
  • مقداردهی و چک کردن بازه‌ها: a..b و x in a..b و x !in a..b

وقتی که عملوندهای a و b نوع ایستای ممیز شناور (Float) یا Double داشته باشند، عملیات بر روی اعداد و بازه‌هایی که تشکیل می‌دهند منطبق با استاندارد IEEE 754 برای اعداد ممیز شناور است.

با این حال برای پشتیبانی از موارد استفاده عمومی و مرتب سازی، وقتی که عملوندها به صورت ایستا یا استاتیک از نوع ممیز شناور تعریف نشده باشند، عملیات از تابع‌های equals و compareTo برای Float و Double استفاده می‌کند که منطبق با استاندارد نیستند و:

  • NaN با خودش برابر است
  • NaN از هر عنصری بزرگتر است حتا از POSITIVE_INFINITY
  • ۰٫۰- بزرگ‌تر از ۰٫۰ است

نویسه‌ها یا حروف

نویسه‌ها (حروف یا کاراکترها) از نوع Char هستند. آن‌ها را نمی‌توان مستقیما به عنوان عدد به کار برد.

fun check(c: Char) {
    if (c == 1) { // ERROR: incompatible types
        // ...
    }
}

نویسه‌های لفظی با «’» (Single quote) تعریف می‌شوند مثل ‘a’. نویسه‌های خاص را می‌توان با «\» تعریف کرد. این کاراکترهای پرش مورد تایید کاتلین هستند:

  • \t
  • \b
  • \n
  • \r
  • \’
  • \”
  • \\
  • \$

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

می‌توان یک نویسه یا کاراکتر را به طور صریح به Int تبدیل کرد:

fun decimalDigitValue(c: Char): Int {
    if (c !in '0'..'9')
        throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt() // Explicit conversions to numbers
}

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

انواع منطقی

نوع Boolean‌ متغیرهای منطقی را تعریف می‌کند و مقادیر آن فقط true یا false است. مقادیر منطقی اگر نیاز به ارجاع نول شونده باشد بسته‌بندی (box) می‌شوند.

عملگرهای منطقی کاتلین این‌ها است:

  • جدا کننده تنبل (lazy) یا ||
  • متصل کننده تنبل یا &&
  • نقیض یا !

آرایه‌ها

آرایه‌ها در کاتلین از کلاس Array ایجاد می‌شوند. این کلاس داراس توابع set و get است که با پربار کردن عملگرها به [] تبدیل می‌شود و متغیری به نام size و چند تابع مفید دیگر:

class Array<T> private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit

    operator fun iterator(): Iterator<T>
    // ...
}

برای ساختن یک آرایه می‌توانیم از تابع کتابخانه‌ای arrayOf استفاده و مقادیر را به آن ارسال کنیم. بنابراین arrayOf(1, 2, 3) آرایه [۱, ۲, ۳]  را می‌سازد. به همین  ترتیب می‌توان از تابع کتابخانه‌ای arrayOfNulls برای ساخت یک آرایه با طول داده شده و مقادیر Null استفاده کرد.

راه‌حل دیگر استفاده از تابع سازنده Array است که طول آرایه و یک تابع برای مقداردهی اولیه عناصر را به عنوان ورودی می‌پذیرد و آرایه را ایجاد می‌کند:

// Creates an Array<String> with values ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -> (i * i).toString() })

همانطور که پیشتر گفتیم عملگر [] را می‌توان به جای صدا زدن توابع get و set صدا زد.

توجه داشته باشید که آرایه‌های کاتلین برخلاف جاوا یکسان و نامتغیر هستند و کاتلین اجازه نمی‌دهد که یک آرایه از نوع String را به یک آرایه از نوع Any نسبت بدهیم و با این کار از خطاهای زمان اجرا جلوگیری می‌کند.

کاتلین کلاس‌های خاصی برای آرایه‌های از انواع اولیه دارد که سربار اضافه بسته‌بندی را ندارند: ByteArray و ShortArray و IntArray و مانند اینها. این کلاس‌ها رابطه ارث بری با کلاس Array ندارند اما توابع و ویژگی‌هایی مشابه دارند. همچنین هر کدام آ‌ن‌ها یک متد سازنده دارند:

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]

رشته‌ها

رشته‌ها با نوع String ساخته می‌شوند. رشته‌ها تغییر ناپذیر (immutable) هستند. عناصر تشکیل دهنده رشته‌ها نویسه‌ها یا کاراکترها هستند و با عملگر اندیس می‌توان به آن‌ها دسترسی داشت: s[i]. با استفاده از یک حلقه for می‌توان یک رشته را پیمایش کرد:

for (c in str) {
    println(c)
}

مقادیر لفظی رشته‌ها

کاتلین دو نوع مقادیر رشته‌ای لفظی دارد: رشته‌هایی که ممکن است در بین آن‌ها کاراکترهای پرش وجود داشته باشد. برای پرش از روی کاراکترهای خاص همانند جاوا از بک اسلش «\» استفاده می‌شود:

val s = "Hello, world!\n"

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

val text = """
    for (c in "foo")
        print(c)
"""

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

val text = """
    |Tell me and I forget.
    |Teach me and I remember.
    |Involve me and I learn.
    |(Benjamin Franklin)
    """.trimMargin()

به صورت پیش فرض از کاراکتر «|» به عنوان پیشوند حاشیه استفاده می‌شود اما می‌توانید آن را به شکل زیر با یک کاراکتر دیگر جایگزین کنید:

\
val text = """
    |Tell me and I forget.
    |Teach me and I remember.
    |Involve me and I learn.
    |(Benjamin Franklin)
    """.trimMargin(">")

رشته‌های قالبی

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

val i = 10
val s = "i = $i" // evaluates to "i = 10"

یا هر عبارت محاسباتی که درون { و } قرار می‌گیرد:

val s = "abc"
val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3"

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

val price = """
${'$'}۹٫۹۹
"""

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

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

  1. یگانی

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

    پاسخ

دیدگاهتان را بنویسید

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