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

انواع داده ساده

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

اعداد

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

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

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

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

مقادیر لفظی ثابت

برای اعداد صحیح چند نوع متغیر لفظی ثابت وجود دارد:

  • دسیمال یا دهدهی: ۱۲۳
    • اعداد از نوع Long با حرف L مشخص می‌شوند: ۱۲۳L
  • هگزادسیمال (یا شانزدهی): ۰x0F
  • باینری یا دودویی:  0b00001011

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

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

  • پیش‌فرض نوع Double است: ۱۲۳٫۵ یا ۱۲۳٫۵e10
  • مقادیر Float با برچسب f یا F مشخص می‌شوند: ۱۲۳٫۵f

زیرخط در مقادیر لفظی عددی (از نسخه ۱.۱)

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

 

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 ذخیره می‌شوند مگر این که ارجاع به عدد null شونده یا انواع ژنریک را بخواهیم. در این حالت از بسته‌بندی (boxing) استفاده می‌کنیم.

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

[/code]val a: Int = 10000
println(a === a) // Prints ‘true’
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA === anotherBoxedA) // !!!Prints ‘false’!!![/code]

اما حافظ برابری است:

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

تبدیل نوع صریح

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

// کد فرضی که کامپایل نمی‌شود
val a: Int? = 1 // A boxed Int (java.lang.Integer)
val b: Long? = a // implicit conversion yields a boxed Long (java.lang.Long)
print(b == a) // Surprise! This prints "false" as Long's equals() checks whether the other is 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
print(i)

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

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

پشتیبانی نکردن از تبدیل نوع ضمنی به ندرت ممکن است توجهی به آن بشود چون نوع از محیط اجرا می‌توان به نوع داده پی برد و عملیات ریاضی برای تبدیل نوع متناسب «سربارگذاری» (overload) شده‌اند، مثلاْ

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

عملیات

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

مثلا درباره عملیات بر روی بیت‌ها، هیچ کاراکتر مخصوصی برای آن‌ها وجود ندارد و فقط توابعی وجود دارد که به شکل infix می‌توان آن‌ها را فراخوانی کرد، مثلاً

val x = (1 shl 2) and 0x000FF000

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

  • shl(bits) – signed shift left (Java’s <<)
  • shr(bits) – signed shift right (Java’s >>)
  • ushr(bits) – unsigned shift right (Java’s >>>)
  • and(bits) – bitwise and
  • or(bits) – bitwise or
  • xor(bits) – bitwise xor
  • inv() – bitwise inversion

مقایسه اعداد اعشاری

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

  • مقایسه برابری: 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 یا انواع null شونده متناظر باشند (یعنی با این نوع اعلان شده باشند یا در نتیجه «تبدیل هوشمند» به این انواع پی برده باشیم)، آنگاه عملیات بر روی اعداد و بازه‌هایی که آن‌ها می‌سازند از «استاندارد IEEE 754 برای محاسبات اعداد اعشاری» پیروی می‌کند.

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

  • NaN با خودش برابر در نظر گرفته می‌شود
  • NaN از هر عنصری بزرگ‌تر در نظر گرفته می‌شود حتا از POSITIVE_INFINTY (مثبت بی‌نهایت)
  • -۰٫۰ (صفر منفی) از ۰٫۰ (صفر مثبت) کوچک‌تر در نظر گرفته می‌شود

نویسه‌ها (کاراکترها)

نویسه‌ها با نوع Char نمایش داده می‌شوند. آن‌ها را نمی‌توان مستقیما به صورت عدد در نظر گرفت

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

مقادیر لفظی نویسه‌ها در علامت نقل قول تکی (یا  single quotes) قرار می‌گیرد: ‘۱’. از نویسه‌های ویژه می‌توان با یک بک اسلش پرید. دنباله‌های پرشی زیر در کاتلین پشتیبانی می‌شود: 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
}

همانند اعداد، هر زمان که به یک ارجاع null شونده از یک نویسه یا کاراکتر احتیاج داشته باشیم از بسته‌بندی یا boxing استفاده می‌کنیم. عملیات بسته‌بندی حافظ یکتایی نیست.

بولین‌ها

از نوع Boolean برای نمایش مقادیر بولین استفاده می‌شود که فقط دو مقدار دارند true و false.

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

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

  • || : انفصال سست 
  • &&: اتصال سست 
  • !: نقیض

آرایه‌ها

آرایه‌ها در کاتلین با کلاس Array نمایش داده می‌شوند که توابع set و get (که با استفاده از سربارگذاری عملگرها به [] تبدیل می‌شود) و ویژگی size و چند تابع عضو مفید دیگر دارد:

class Array&amp;amp;amp;lt;T&amp;amp;amp;gt; private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit

    operator fun iterator(): Iterator&amp;amp;amp;lt;T&amp;amp;amp;gt;
    // ...
}

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

یک راه دیگر استفاده از سازنده کلاس Array است که اندازه آرایه و یک تابع که مقادیر اولیه همه عناصر آرایه در اندیس را برمی‌گرداند، است.

// Creates an Array&amp;amp;amp;lt;String&amp;amp;amp;gt; with values ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -&amp;amp;amp;gt; (i * i).toString() })
asc.forEach { println(it) }

همانطور که در بالا گفتیم، عملگر [] معادل فراخوانی توابع عضو set و get است.

نکته: در کاتلین بر خلاف جاوا آرایه‌ها نامتغیر و ثابت هستند. یعنی کاتلین اجازه نمی‌دهد که یک Arrat<String> را به یک Array<Any> نسبت بدهیم. با این کار، کاتلین از خطاهای زمان اجرا جلوگیری می‌کند (البته می‌توانید از Array<out Any> استفاده کنید. بخش Type Projections را ببینید).

کاتلین همچنین کلاس‌های خاصی برای ساختن آرایه‌هایی از انواع داده اولیه دارد که هزینه اضافه بسته‌بندی (boxing) را ندارند: ByteArray، ShortArray و IntArray و مانند آن. این کلاس‌ها رابطه وراثتی با کلاس Array ندارند اما مجموعه توابع عضو مشابهی دارند و هر کدام از آن‌ها متد فکتوری متناظری دارند:

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

رشته‌ها

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

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

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

val s = "abc" + 1
println(s + "def")

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

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

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

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