انواع داده ساده
در کاتلین هر چیزی یک شی است به طوری که میتوان متدهای عضو و ویژگیها را روی هر متغیری صدا زد. بعضی از انواع میتوانند پیادهسازی از نوع اولیه داشته باشند، مثلا اعداد، کاراکترها و متغیرهای بولین میتوانند در زمان اجرا به عنوان انواع اولیه در نظر گرفته شوند اما از منظر کاربر آنها شبیه کلاسهای متداول به نظر میرسند. در این بخش ما انواع داده ساده که در کاتلین استفاده میشود را توضیح میدهیم: اعداد، کاراکترها، بولینها، آرایهها و رشتهها.
اعداد
اعداد در کاتلین بسیار شبیه به جاوا هستند ولی صد درصد یکی نیستند. برای مثال تبدیل بازه ضمنی برای اعداد وجود ندارد و مقادیر لفظی (لیترال) در برخی موارد تفاوتهایی (با جاوا) دارند.
کاتلین برای اعداد انواع زیر را دارد که به جاوا بسیار نزدیک است:
نوع | طول (به بیت) |
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 andor(bits)
– bitwise orxor(bits)
– bitwise xorinv()
– 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;lt;T&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;lt;T&amp;amp;gt; // ... }
برای ساخت یک آرایه میتوانیم از تابع کتابخانهای ()arrayOf استفاده کنیم و مقادیر را به آن بفرستیم. بنابراین arrayOf(1,2,3) آرایه [۱,۲,۳] را میسازد. از سوی دیگر با استفاده از تابع کتابخانهای ()arrayOfNulls میتوان یک آرایه با اندازه داده شده با عناصر null ساخت.
یک راه دیگر استفاده از سازنده کلاس Array است که اندازه آرایه و یک تابع که مقادیر اولیه همه عناصر آرایه در اندیس را برمیگرداند، است.
// Creates an Array&amp;amp;lt;String&amp;amp;gt; with values ["0", "1", "4", "9", "16"] val asc = Array(5, { i -&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")
به خاطر داشته باشید در بیشتر مواقع استفاده از قالبهای رشتهای یا رشتههای خام به متصل کردن رشتهها ارجحیت دارد.