این فصل را پوشش می دهد
بخش اول فصل اول را در اینجا مشاهده کنید
در این فصل، شما یاد خواهید گرفت که چگونه عناصر ضروری هر برنامه را در کاتلین استفاده کنید: متغیرها، توابع و کلاس ها. در طول مسیر، با مفهوم properties در کاتلین آشنا خواهید شد.
شما استفاده از structures های کنترلی مختلف را در کاتلین یاد خواهید گرفت. آنها بیشتر شبیه جاوا هستند که برای شما آشنا هستند، اما به روش های زیادی برای استفاده راحت بهتر شده.
ما مفهوم castهای هوشمند را معرفی میکنیم که یک بررسی نوع داده و یک cast را در یک عملیات ترکیب میکنند. در نهایت، ما در مورد مدیریت استثنا (exception ) صحبت خواهیم کرد. در پایان این فصل، میتوانید از اصول اولیه زبان برای نوشتن کد کاتلین استفاده کنید، حتی اگر کد کوتاه و کامل نباشد.
این بخش شما را با عناصر پایه ای که هر برنامه کاتلین از آن تشکیل شده است آشنا می کند: توابع و متغیرها. خواهید دید که چگونه کاتلین به شما اجازه می دهد تا بسیاری از نوع داده ها را حذف کنید و چگونه شما را تشویق می کند که ترجیحا از داده های تغییرناپذیر (immutable) نسبت به قابل تغییر(mutable) استفاده کنید.
بیایید با مثال کلاسیک شروع کنیم: برنامه ای که "Hello, world!" را چاپ می کند. در کاتلین، این فقط یک تابع است:
چه ویژگی ها و بخش هایی از syntax زبان را می توانید در این قطعه کد ساده مشاهده کنید؟ این لیست را بررسی کنید:
تا اینجا که خیلی خوب بود! بعداً درباره بعضی از این موضوعات با جزئیات بیشتر صحبت خواهیم کرد. حالا، بیایید syntax تابع (function) را بررسی کنیم.
شما دیدید که چگونه می توان تابعی (function ) نوشت که چیزی برای بازگشت(return) ندارد. اما در کجا باید نوع داده بازگشتی برای تابعی که نتیجه دلخواهمون رو بده قرار داد؟ می توانید حدس بزنید که باید جایی بعد از لیست پارامترها برود:
نوشتن تابع با کلمه کلیدی fun شروع می شود و پس از آن نام تابع: max، در این مورد. بعد اون لیست پارامترها داخل پرانتز قرار می گیرد. نوع بازگشتی بعد از لیست پارامترها قرار می گیرد که با یک دونقطه از آن جدا شده است.
شکل 2.1 ساختار اصلی یک تابع را به شما نشان می دهد. توجه داشته باشید که در کاتلین if یک عبارت شرطی با مقدار نتیجه مشخص است. شبیه یک عملگر سه تایی در جاوا است: a : b ? (a > b)
در کاتلین، if یک expression است، نه یک statement . تفاوت بین یک statement و یک expression در این است که یک expression دارای یک مقدار (value )است که می تواند به عنوان بخشی از expression دیگر استفاده شود، در حالی که یک statement همیشه یک عنصر سطح بالا در بلوک خود است و مقدار (value )خاص خود را ندارد. در جاوا تمام structures کنترلی، statements هستند. در کاتلین، اکثر structures کنترل، به جز حلقهها (for، do، و do/ while) expressions هستند. توانایی ترکیب structures کنترل با expressionهای دیگر به شما امکان می دهد بسیاری از الگوهای(patterns) رایج را به طور مختصر بنویسید، همانطور که در ادامه کتاب خواهید دید.
از طرف دیگر، expressions هایی در جاوا هستند که در کاتلین به statements تبدیل می شوند. این به جلوگیری از سردرگمی بین مقایسه ها و جایگزینی(assignments) کمک می کند، که منبع رایج اشتباهات است.
می توانید function قبلی را حتی بیشتر ساده کنید. از آنجا که بدنه آن از یک expression واحد تشکیل شده است، می توانید از آن expression به عنوان کل بدنه تابع استفاده کنید و پرانتزهای تکراری و بازگشت statement را حذف کنید:
fun max(a: Int, b: Int): Int = if (a > b) a else b
داخل if براکت function و بدنه آن داخل پرانتز نوشته شده، می گوییم که این تابع دارای بدنه بلوکی(block body) است. اگر یک expression را مستقیماً برگرداند، دارای expression body است.
نکته : INTELLIJ IDEA IntelliJ IDEA قصد دارد برای تبدیل بین دو styles، توابعی را فراهم کند: “Convert to expression body” and “Convert to block body.”
توابع با بدنه expression اغلب در کد کاتلین یافت می شوند. این سبک نه تنها برای توابع تک خطی بی اهمیت، بلکه برای توابعی که یک expression منفرد و پیچیده تر را ارزیابی می کنند، مانند if، when یا try استفاده می شود. چنین توابعی را بعداً در این فصل خواهید دید، زمانی که در مورد ساختار When صحبت می کنیم.
می توانید تابع max را حتی بیشتر ساده کرده و نوع بازگشت را حذف کنید:
fun max(a: Int, b: Int) = if (a > b) a else b
چرا توابعی بدون نوع بازگشت وجود دارد؟ آیا کاتلین، به عنوان یک زبان تایپ ایستا(static )، نیازی به داشتن یک نوع داده در زمان کامپایل برای هر عبارت ندارد؟ در واقع، هر متغیر و هر expression یک نوع داده دارد و هر تابعی یک نوع داده بازگشتی دارد. اما برای بدنه توابع expression ، کامپایلر میتواند عبارت مورد استفاده به عنوان بدنه تابع را تجزیه و تحلیل کند و از نوع داده آن بهعنوان نوع داده بازگشت تابع استفاده کند، حتی زمانی که به صراحت بیان نشده باشد. این نوع تحلیل را معمولاً type inference می نامند.
توجه داشته باشید که حذف نوع داده بازگشتی فقط برای توابعی با بدنه expression مجاز است. برای توابعی با بدنه بلوکی (block )که مقداری را برمی گرداند، باید نوع بازگشت را مشخص کنید و statements بازگشتی را به صراحت بنویسید. این یک انتخاب آگاهانه است . یک تابع که تو دنیای واقعی استفاده میشه اغلب طولانی است و می تواند حاوی چندین statements بازگشتی باشد. داشتن نوع داده بازگشتی و statements بازگشتی به درک سریع چیزی که برگردانید کمک می کند. بیایید در ادامه به syntax متغیرها نگاه کنیم.
در جاوا،برای نوشتن یک متغیر ،با یک نوع داده شروع می کنید. این برای کاتلین کار نمی کند، چون به شما امکان می دهد انواع داده های موجود را از نوشتن متغیر های بسیاری حذف کنید. بنابراین در کاتلین شما با یک کلمه کلیدی شروع میکنید و ممکن است (یا نه) نوع داده را بعد از نام متغیر قرار دهید. بیایید دو متغیر را اعلام کنیم:
val question = "The Ultimate Question of Life, the Universe, and Everything" val answer = 42
این مثال نوع داده را حذف میکند، اما در صورت تمایل میتوانید نوع را به صراحت مشخص کنید:
val answer: Int = 42
درست مانند بدنه توابع اکسپرشن(expression-body functions )، اگر نوع را مشخص نکنید، کامپایلرمقدار دهی اولیه expression را تحلیل می کند و از نوع داده آن به عنوان نوع داده متغیر استفاده می کند. در این حالت، مقدار اولیه، 42، دارای نوع داده Int است، بنابراین متغیر دارای همان نوع داده خواهد بود.
اگر از یک عدد اعشاری ثابت استفاده کنید، متغیر دارای نوع Double خواهد بود:
انواع اعداد را عمیقتر در بخش 6.2 پوشش داده شده است.
اگر متغیری مقداردهی اولیه نشده است ، باید نوع آن را به صراحت مشخص کنید:
val answer: Int answer = 42
اگر اطلاعاتی در مورد مقادیری که میتوان به این متغیر نسبت داد ندهید ، کامپایلر نمیتواند نوع داده را تشخیص دهد.
دو کلمه کلیدی برای تعریف یک متغیر وجود دارد:
به طور پیش فرض، شما باید سعی کنید تا همه متغیرها را در کاتلین با کلمه کلیدی val بنویسید. فقط در صورت لزوم از var استفاده کنید. استفاده از منابع تغییرناپذیر، اشیاء تغییرناپذیر(immutable objects) و توابع بدون effects ، کد شما را به سبک functional نزدیکتر می کند. در فصل 1 به طور مختصر به مزایای آن پرداختیم و در فصل 5 به این موضوع بیشتر میپردازیم.
یک متغیر val باید دقیقاً یک بار در طول اجرای بلوکی که در آن تعریف شده است مقداردهی اولیه شود. اما بسته به شرایط میتونید آن را با مقادیر مختلف مقداردهی اولیه کنید، اگر کامپایلر مطمئن باشد که فقط یکی از دستورات(statements ) مقدار دهی اولیه اجرا میشود:
val message: String if (canPerformOperation()) { message = "Success" // ... perform the operation } else { message = "Failed" }
توجه : حتی اگر یک مرجع val تغییرناپذیر (immutable)است و نمی توان آن را تغییر داد، object که به آن اشاره می کند ممکن است تغییر پذیر (mutable) باشد. به عنوان مثال، این کد کاملا معتبر است:
در فصل 6، اجسام قابل تغییر و تغییرناپذیر را با جزئیات بیشتری مورد بحث قرار خواهیم داد. حتی اگر کلمه کلیدی var به متغیر اجازه تغییر مقدار خود را دهد، نوع داده آن ثابت است.
به عنوان مثال، این کد کامپایل نمی شود:
خطای string literal وجود دارد چون متغییر (Int) است ولی انتظار می رود (String) باشد. کامپایلر نوع متغیر را فقط از مقدار دهی اولیه ساز تشخیص میدهد و مقدار دهی های بعدی را هنگام تعیین نوع داده در نظر نمی گیرد.
اگر نیاز دارید مقداری از نوع داده دیگری را در یک متغیر که نوع داده دیگری دارد ذخیره کنید، باید به صورت دستی مقدار را به نوع داده مناسب تبدیل یا وادار کنید. در بخش 6.2.3 در مورد تبدیل نوع داده اولیه بحث خواهیم کرد..
الان که میدانید چطوری متغیرها را تعریف کنید، وقت آن است که چند ترفند جدید برای مقدار دادن به متغییر ها یاد بگیریم.
بیایید به مثال "Hello World" برگردیم و این بخش را باز کنیم. در اینجا نحوه سلام دادن به کاربران با نام خود کاربر را با زبان کاتلین را میبینیم:
تو این مثال میخوایم یک feature رو از string templatesمعرفی کنید. در کد، بعد از تعیین نام متغییر از آن در string literal استفاده میکنید. مانند بسیاری از زبان های scripting ، درکاتلین هم میتونید به متغیرهای محلی در string literals رجوع کنید با قرار دادن کاراکتر $ در جلوی نام متغیر. این دقیقا مثل این کد جاوا هست: ("Hello, " + name + "!") اما فشردتر و به همان اندازه کارامدتر. و البته، expressions ها به صورت ایستاتیک بررسی می شوند و اگر بخواهید به متغیری اشاره کنید که وجود ندارد، کد کامپایل نمی شود.
اگر بخواهید کاراکتر $ را در یک رشته قرار دهید، و از محدودیت های ان فرار می کنید: println("\$x")
x$ را چاپ می کند و x را به عنوان متغیر شناسایی یا تفسیر نمی کند.
شما محدود نیستید به نام متغیرهای ساده. می توانید از expressionهای کامل تری استفاده کنید. فقط با گذاشتن براکت هایی در اطراف expression ها به راحتی میتونید استفاده کنید:
شما همچنین می توانید نقل قول های دوگانه را در داخل دو نقل قول قرار دهید ، به شرطی که در یک عبارت باشند:
fun main(args: Array) { println("Hello, ${if (args.size > 0) args[0] else "someone"}!") }
بعداً، در بخش 3.5، به رشتهها (strings ) باز میگردیم و درباره کارهایی که میتوانید با آنها انجام دهید بیشتر صحبت خواهیم کرد.
حالا که میدونید چطور توابع و متغیرها را بسازید . بیایید یک پله در سلسله مراتب بالا برویم و به کلاس ها نگاه کنیم. این بار، از مبدل جاوا به کاتلین برای کمک به شما برای شروع استفاده از ویژگی های زبان جدید استفاده خواهید کرد.
احتمالاً در برنامه نویسی شی گرا تازه کار نیستید و با abstraction یک کلاس آشنا هستید. مفاهیم کاتلین در این زمینه برای شما آشنا هستند، اما میبینید که بسیاری از کارهای رایج را می توان با کد بسیار کمتری انجام داد. این بخش شما را با سینتکس (syntax ) اولیه برای ساخت کلاس ها آشنا می کند. در فصل 4 به جزئیات بیشتر خواهیم پرداخت.
برای شروع ، بیایید به یک کلاس ساده جاوا Person نگاه کنیم که تا کنون فقط یک ویژگی ، name دارد .
Listing 2.3 Simple Java class Person
/* Java */
public class Person { private final String name; public Person(String name) { this.name = name; } public String getName() { return name; } }
در جاوا، بدنه سازنده (constructor ) اغلب حاوی کدهایی است که کاملاً تکراری است: پارامترها را به فیلدهایی با نام های مربوطه اختصاص می دهد. در کاتلین، این منطق را می توان بدون این همه تکرار بیان کرد.
در بخش 1.5.6، مبدل جاوا به کاتلین را معرفی کردیم: ابزاری که به طور خودکار کد جاوا را با کد کاتلین جایگزین می کند که همان کار را انجام می دهد. بیایید به مبدل در عمل نگاه کنیم و کلاس Person را به Kotlin تبدیل کنیم.
Listing 2.4 Person class converted to Kotlin
class Person(val name: String)
جالبه مگه نه ؟ اگر زبان مدرن JVM دیگری را امتحان کرده باشید، شاید چیزی مشابه این دیده باشید. کلاس هایی از این نوع (که فقط شامل داده ها هستند اما هیچ کدی ندارند) اغلب اشیاء value نامیده می شوند و بسیاری از زبان ها یک syntax مختصر برای اعلام آنها ارائه می دهند.
توجه داشته باشید که modifier public در هنگام تبدیل از جاوا به کاتلین ناپدید شد. در کاتلبن، public پیشفرض است، پس میتوانید آن را حذف کنید.
همانطور که بدون شک می دانید، ایده کلاس این است که داده ها و کدهایی را که روی آن داده ها کار می کنند در یک موجودیت واحد کپسوله کند. در جاوا داده ها در فیلدهایی که معمولا خصوصی (private) هستند ذخیره می شود. اگر نیاز دارید که به کاربران کلاس اجازه دهید به آن داده ها دسترسی داشته باشند، روش های دسترسی را ارائه می دهید: یک گیرنده (getter) و احتمالاً یک setter . نمونه ای از این را در کلاس Person دیدید. Setter همچنین می تواند حاوی کد های منطقی اضافی برای اعتبارسنجی مقدار ارسال شده، ارسال اعلان در مورد تغییر مقدار و غیره باشد.
در جاوا، معمولاً از ترکیب فیلد و دسترسیهای آن به عنوان یک ویژگی یاد میشود و بسیاری از فریمورکها از آن مفهوم استفاده زیادی میکنند. در کاتلین، properties ها خصوصیات زبان درجه یک هستند که به طور کامل جایگزین فیلدها و methods های دسترسی می شوند. روش اعلام یک property دقیقا مثل اعلام یک متغییر است : با کلمات کلیدی val و var. ویژگی (property) اعلام شده به عنوان val فقط خواندنی است، در حالی که ویژگی var قابل تغییر است و می توان آن را تغییر داد.
اساساً، هنگامی که یک ویژگی (property) را اعلام می کنید، دسترسی های مربوطه را اعلام می کنید (گیرنده (getter) برای یک ویژگی فقط خواندنی، و هر دو getter و setter برای یک ویژگی قابل نوشتن است). بهطور پیشفرض، پیادهسازی دسترسی ها ( Accessor ) بیاهمیت است: یک فیلد برای ذخیره مقدار ایجاد میشود و getter و setter مقدار آن را برمیگرداند و بهروزرسانی میکند. اما اگر بخواهید، میتوانید یک دسترسی سفارشی را اعلام کنید که از منطق متفاوتی برای محاسبه یا بهروزرسانی مقدار ویژگی استفاده میکند.
اعلام مختصر شده کلاس Person در لیست 2.5، همان پیادهسازی اصولی کد اصلی جاوا را پنهان میکند: کلاسی با فیلدهای خصوصی که در سازنده (constructor) مقداردهی اولیه میشود و میتوان از طریق getter مربوطه به آن دسترسی داشت. یعنی که شما می توانید از این کلاس از جاوا و از کاتلین به همان روش استفاده کنید، مستقل از جایی که در آن اعلام شده است. نحوه استفاده یکسان به نظر می رسد. در اینجا نحوه استفاده از کلاس Person در کد جاوا آمده است.
توجه داشته باشید که وقتی Person در جاوا و در کاتلین تعریف شده باشد، این به نظر می رسد. ویژگی name درکاتلین به عنوان یک متد getter به نام getName در جاوا قابل دیدن است. قانون نامگذاری getter و setter یک استثنا دارد: اگر نام ویژگی با isشروع شود، هیچ پیشوند اضافی برای getter اضافه نمی شود و در نام setter ، is با set جایگزین می شود. بنابراین، از جاوا، isMarried() را فراخوانی می کنید.
اگر لیست 2.6 را به کاتلین تبدیل کنید ، نتیجه زیر را دریافت خواهید کرد.
اکنون به جای فراخوانی گیرنده(getter)، مستقیماً به property ارجاع می دهید. منطق ثابت می ماند، اما کد مختصرتر می شود. مجموعهای از propertiesهای قابل تغییر به همین صورت عمل میکنند: در حالی که در جاوا، از person.setMarried(false) برای اطلاع از طلاق استفاده میکنید. در کاتلین می توانید person.isMarried = false بنویسید.
نکته شما همچنین می توانید از دستور خصوصیت کاتلین برای کلاس های تعریف شده در جاوا استفاده کنید. دریافتکنندههای کلاس جاوا را میتوان بهعنوان ویژگیهای(property) val از کاتلین ، و جفتهای getter/setter را میتوان بهعنوان ویژگی (property) varدسترسی داشت. به عنوان مثال، اگر یک کلاس جاوا متدهایی به نام getName و setName را تعریف کند، می توانید به عنوان یک property به نام name به آن دسترسی داشته باشید. اگر متدهای isMarried و setMarried را تعریف کند، نام property کاتلین مربوطه Marriedخواهد بود.
در بیشتر موارد، property یک فیلد پشتیبان دارد که مقدار property را ذخیره می کند. اما اگر بتوان مقدار را مثلا، از propertyهای دیگر در لحظه محاسبه کرد آن را با استفاده از یک گیرنده (getter) سفارشی بیان کنید.
این بخش به شما نشان می دهد که چگونه یک پیاده سازی سفارشی از یک دسترسی به property بنویسید. فرض کنید یک مستطیل را اعلام می کنید که می تواند بگوید مربع است یا خیر. شما نیازی به ذخیره آن اطلاعات به عنوان یک فیلد جداگانه ندارید، زیرا می توانید بررسی کنید که آیا ارتفاع با عرض برابر است یا خیر:
ویژگی isSquare برای ذخیره مقدار خود نیازی به فیلد ندارد. این فقط یک گیرنده(getter) سفارشی با پیاده سازی ارائه شده دارد. با هر بار دسترسی به property ، مقدار محاسبه می شود.
توجه داشته باشید که لازم نیست از syntax های کامل با پرانتز و اکولاد استفاده کنید. می توانید get() = height == width را نیز بنویسید. فراخوانی چنین خاصیتی ثابت می ماند:
>>> val rectangle = Rectangle(41, 43) >>> println(rectangle.isSquare) false
اگر نیاز به دسترسی به این property از جاوا دارید، روش isSquare را مانند قبل فراخوانی می کنید.
ممکن است بپرسید که آیا بهتر است یک تابع(function) بدون پارامتر اعلام شود یا یک property با یک گیرنده(getter) سفارشی. هر دو گزینه مشابه هستند: هیچ تفاوتی در اجرا یا عملکرد وجود ندارد. آنها فقط در خوانایی متفاوت هستند. به طور کلی، اگر ویژگی (property) یک کلاس را توصیف کنید، باید آن را به عنوان یک property اعلام کنید.
در فصل 4، نمونههای بیشتری را که از کلاسها و ویژگیها(property) استفاده میکنند ارائه میکنیم ، و به syntax که سازندهها (constructors) را به صراحت اعلام میکنند، نگاه میکنیم. اگر در این مدت بی حوصله هستید، همیشه می توانید از مبدل جاوا به کاتلین استفاده کنید. اکنون اجازه دهید قبل از اینکه به بحث در مورد سایر ویژگی های زبان بپردازیم، به طور خلاصه نحوه سازماندهی کد کاتلین روی دیسک را بررسی می کنیم.
می دانید که جاوا همه کلاس ها را در بسته ها(packages) سازماندهی می کند. کاتلین هم مثل جاوا مفهوم بسته ها(packages) را دارد. هر فایل کاتلین می تواند در ابتدا یک دستور بسته(packages) داشته باشد و تمام اعلانات (کلاس ها، توابع و خصوصیات(properties)) تعریف شده در فایل در آن بسته (package) قرار می گیرد. اعلانهای تعریفشده در فایلهای دیگر اگر در یک بسته(package) باشند، میتوانند مستقیماً استفاده شوند و اگر در بسته بندی(package) متفاوتی هستند باید import شوند. مانند جاوا، دستورهای import در ابتدای فایل قرار می گیرند و از کلمه کلیدی import استفاده می کنند. در اینجا مثالی از یک فایل منبع اوردیم که syntax اعلام بسته و اعلام import را نشان می دهد.
کاتلین بین وارد (import) کردن کلاسها و توابع (functions) تمایزی قائل نمیشود و به شما امکان میدهد هر نوع اعلانی را با استفاده از کلمه کلیدی importوارد کنید. می توانید تابع سطح بالا را با نام وارد کنید.
شما همچنین می توانید تمام اعلان های تعریف شده در یک بسته خاص را با قرار دادن ستاره (*) نام بسته وارد (import) کنید. توجه داشته باشید که این وارد کردن ستاره نه تنها کلاس های تعریف شده در بسته، بلکه توابع و ویژگی های سطح بالا را نیز قابل مشاهده می کند. در لیست 2.9، نوشتنimport geometry.shapes.* به جای import کاملی که بالا نوشتیم باعث می شود که کد به درستی کامپایل شود.
در جاوا، کلاس های خود را در ساختاری از فایل ها و دایرکتوری ها قرار می دهید که با ساختار بسته (package) مطابقت دارد. به عنوان مثال، اگر بستهای به نام shapes با چندین کلاس دارید، باید هر کلاس را در یک فایل جداگانه با یک نام منطبق قرار دهید و آن فایلها را در دایرکتوری که shapes نیز نامیده میشود ذخیره کنید. شکل 2.2 نشان می دهد که چگونه بسته هندسی و زیر بسته های آن می توانند در جاوا سازماندهی شوند. فرض کنید که تابع createRandomRectangle در یک کلاس جداگانه به نام RectangleUtilقرار دارد.
شکل 2.2 در جاوا ، سلسله مراتب فهرست ، سلسله مراتب بسته را کپی می کند.
در کاتلین می توانید چندین کلاس را در یک فایل قرار دهید و هر نامی را برای آن فایل انتخاب کنید. کاتلین همچنین هیچ محدودیتی برای چیدمان فایل های منبع روی دیسک حافظه اعمال نمی کند. شما می توانید از هر ساختار دایرکتوری برای سازماندهی فایل های خود استفاده کنید. به عنوان مثال، شما می توانید تمام محتوای هندسی را روی یک بسته(package) تعریف کنید. اشکال را در فایل shapes.kt قرار دهید و این فایل را بدون ایجاد پوشه shapesجداگانه در پوشه هندسه قرار دهید (شکل 2.3 را ببینید).
شکل 2.3 سلسله مراتب بسته شما نیازی به رعایت سلسله مراتب دایرکتوری ندارد.
با این حال، در بیشتر موارد، پیروی از طرحبندی دایرکتوری جاوا(directory layout) و سازماندهی فایلهای منبع در دایرکتوریها بر اساس ساختار بسته(package)، همچنان یک تمرین خوب است. پایبندی به این ساختار به ویژه در پروژههایی که کاتلین با جاوا ترکیب شده است بسیار مهم است، زیرا انجام این کار به شما امکان میدهد تا کد را به تدریج و بدون هیچ گونه surprises منتقل کنید. اما شما نباید از نوشتن چندین کلاس به یک فایل دریغ کنید، به خصوص اگر کلاس ها کوچک هستند (و اغلب تو کاتلین اینجوریه).
حالا که شما ساختار برنامه هارو میدونید. بیایید با یادگیری مفاهیم اولیه و ساختارهای کنترل در کاتلین ادامه بدیم.
در این بخش قصد داریم در مورد ساخت when صحبت کنیم. می توان آن را به عنوان جایگزینی برای switch construct در جاوا در نظر گرفت، اما قدرتمندتر است و بیشتر مورد استفاده قرار می گیرد. در طول مسیر، نمونه ای از اعلان enums در کاتلین را به شما ارائه می دهیم و در مورد مفهوم smart casts بحث می کنیم.
بیایید با افزودن imaginary bright pictures به این کتاب جدی و بررسی مجموعه ای از رنگها شروع کنیم.
فهرست 2.10 اعلام کلاس enum ساده
enum class Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
این یک مورد نادر است زمانی که یک اعلام کاتلین از کلمات کلیدی بیشتری نسبت به کد جاوا استفاده می کند: کلاس enum در مقابل just enum در جاوا. در کاتلین، enum یک کلمه کلیدی به اصطلاح نرم است: وقتی قبل از کلاس می آید معنای خاصی دارد، اما می توانید در جاهای دیگر از آن به عنوان یک نام معمولی استفاده کنید. از طرف دیگر، classهمچنان یک کلمه کلیدی است و شما همچنان متغیرهایی به نام clazz یا aClass را اعلام خواهید کرد.
همانطور که در جاوا، enum ها لیستی از مقادیر نیستند: می توانید properties ها و متدها را در کلاس های enum اعلام کنید. در اینجا نحوه عملکرد آن آمده است.
ثابتهای Enum از همان ساختار constructor و اعلان propertyاستفاده میکنند که قبلاً برای کلاسهای معمولی دیدید. وقتی هر enum را ثابت اعلام میکنید، باید مقادیر خاصیت آن ثابت را ارائه دهید. توجه داشته باشید که این مثال تنها جایی را در syntax کاتلین نشان می دهد که در آن باید از سیمیکالن استفاده کنید: اگر متدی را در کلاس enum تعریف کنید، سیمیکالن لیست ثابت enum را از تعاریف متد جدا می کند. حالا بیایید چند روش جالب برای مقابله با ثابت های enum در کد خود ببینیم.
آیا به یاد دارید که چگونه کودکان از عبارات mnemonic برای حفظ کردن رنگ های رنگین کمان استفاده می کنند؟ یکی از آنها این است: "ریچارد یورک بیهوده نبرد کرد!" تصور کنید به تابعی نیاز دارید که برای هر رنگ یک mnemonic به شما بدهد (و نمی خواهید این اطلاعات را در خود enum ذخیره کنید). در جاوا می توانید از دستور switch برای این کار استفاده کنید. در ساختار کاتلین میتوایند از when استفاده کنید.
دستور when مثل if، عبارتی است که مقداری را برمی گرداند، بنابراین می توانید تابعی را با expression body بنویسید و عبارت When را مستقیماً برمی گرداند. هنگامی که در ابتدای فصل در مورد توابع صحبت کردیم، نمونه ای از یک تابع چند خطی با expression body را قول دادیم. حالا یه مثالی در این مورد رو مورد بررسی قرار میدهیم.
اون بخش از کد مربوط به مقدار رنگ ارسال شده را پیدا می کند. برخلاف جاوا، شما نیازی به نوشتن دستورات break در هر شاخه ندارید (اغلب باگ های کد جاوا ، breakهای فراموش یا گم شده است ). در صورت تطبیق بودن ، فقط شاخه مربوطه اجرا می شود. همچنین می توانید چندین مقدار را در یک شاخه ترکیب کنید اگر آنها را با کاما از هم جدا کنید.
در این مثال ها از enum constants با نام کامل خود استفاده می کنند و نام کلاس رنگ enum را مشخص می کنند. می توانید کد را با وارد کردن مقادیر ثابت ساده کنید.
ساختار When در کاتلین قدرتمندتر از switch در جاوا است. برخلاف switch ، که از شما میخواهد از ثابتها (ثابت enum، رشتهها(strings)، یا اعداد حرفی) بهعنوان شرایط شاخه استفاده کنید، when که به هر شیئی(objects) اجازه میدهد. بیایید تابعی بنویسیم که بتواند دو رنگ را در این پالت کوچک با هم ترکیب کند. شما گزینه های زیادی ندارید و می توانید به راحتی همه آنها را بنویسید.
اگر رنگهای c1 و c2 قرمز و زرد (یا بالعکس) باشند، نتیجه اختلاط آنها نارنجی است و غیره. برای پیاده سازی این، از set برای مقایسه استفاده می کنید. کتابخانه استاندارد کاتلین شامل یک تابع setOf است که set حاوی اشیاء (objects) مشخص شده به عنوان آرگومان های آن را ایجاد می کند. set مجموعه ای است که ترتیب اقلام برای آن مهم نیست. دو مجموعه (set) اگر شامل موارد مشابهی باشند با هم برابرند. بنابراین، اگر مجموعه های setOf(c1, c2) و setOf(RED, YELLOW) برابر باشند، به این معنی است که یا c1 قرمز است و c2 زرد است، یا بالعکس. این دقیقاً همون چیزیه که می خواهیم بررسی کنید.
عبارت When با آرگومان خود با همه شاخه ها مطابقت دارد تا زمانی که یک شرط شاخه برآورده شود. بنابراین setOf(c1, c2) برای مقایسه بررسی می شود: ابتدا با setOf(RED, YELLOW) و سپس با مجموعه رنگ های دیگر، یکی پس از دیگری. اگر هیچ یک از شرایط شاخه دیگر برآورده نشد، شاخه else ارزیابی می شود.
توانایی استفاده از هر عبارتی به عنوان شرط شاخه when به شما امکان می دهد در بسیاری از موارد کد مختصر و زیبا بنویسید. در این مثال، شرط یک بررسی مقایسه ای است. در ادامه خواهید دید که چگونه شرط ممکن است هر عبارت Boolean ای باشد.
ممکن است متوجه شده باشید که فهرست 2.15 تا حدودی ناکارآمد است. هر بار که این تابع را فراخوانی می کنید، چندین نمونه Set ایجاد می کند که فقط برای بررسی اینکه آیا دو رنگ داده شده با دو رنگ دیگر مطابقت دارند یا خیر استفاده می شود. به طور معمول این مشکلی نیست، اما اگر تابع زیاد فراخوانی می شود، ارزش آن را دارد که کد را به روش دیگری بازنویسی کنید تا از ایجاد باگ جلوگیری کنید. شما می توانید این کار را با استفاده از عبارت Whenبدون آرگومان انجام دهید. کد کمتر و قابل خواندن است، اما این هزینه ا است که اغلب باید برای دستیابی به عملکرد بهتر بپردازید.
اگر هیچ آرگومانی برای عبارت Whenارائه نشده باشد، شرط شاخه هر عبارت Boolean است. تابع mixOptimized همان کاری را انجام می دهد که mixقبلا انجام می داد. مزیت آن این است که هیچ شیء اضافی ایجاد نمی کند، اما هزینه آن این است که خواندن آن سخت تر است.
بیایید پیش برویم و به نمونههایی از ساختار when نگاه کنیم که در آن smart casts وارد بازی میشوند
به عنوان مثال برای این بخش، تابعی می نویسیم که عبارات ساده جمع دوعدد مانند (1 + 2) + 4 را ارزیابی می کند. عبارات فقط شامل یک نوع عمل می شوند: مجموع دو عدد. سایر عملیات های حسابی (تفریق، ضرب، تقسیم) را می توان به روشی مشابه اجرا کرد و شما می توانید آن را به عنوان تمرین انجام دهید.
اول، چگونه عبارات را کد می کنید؟ شما آنها را در یک ساختار درخت مانند ذخیره می کنید، جایی که هر گره یک جمع (Sum) یا یک عدد (Num) است. Num همیشه یک گره برگ است، در حالی که یک گره Sum دو فرزند دارد: آرگومان های عملیات sum. فهرست زیر ساختار ساده ای از کلاس های مورد استفاده برای کد کردن عبارات را نشان می دهد: یک interface به نام Expr و دو کلاس Num و Sum که آن را پیاده سازی می کنند. توجه داشته باشید که Expr interface هیچ متدی را اعلام نمی کند. به عنوان یک interface نشانگر برای ارائه یک نوع مشترک برای انواع مختلف عبارات استفاده می شود. برای مشخص کردن اینکه یک کلاس یک interface را پیاده سازی می کند، از یک کولون (:) به دنبال نام interface استفاده می کنید:
عبارت Sum به سمت آرگومان های چپ و راست از نوع Expr را ذخیره می کند. در این مثال کوچک، آنها می توانند Num یا Sum باشند. برای ذخیره عبارت (1 + 2) + 4 که قبلا ذکر شد، یک شی Sum(Sum(Num(1)، Num(2))، Num(4)) ایجاد می کنید. شکل 2.4 نمایش درختی آن را نشان می دهد
حال بیایید نحوه محاسبه مقدار یک عبارت را بررسی کنیم. محاسبه عبارت مثلا باید 7 را برگرداند:
>>> println (eval(Sum(Sum(Num(1), Num(2)), Num (4)))) 7
این Expr interface دو پیاده سازی دارد، بنابراین شما باید دو گزینه را امتحان کنید تا مقدار نتیجه را برای یک عبارت محاسبه کنید:
ابتدا به این تابع که به روش معمولی جاوا نوشته شده است نگاه می کنیم و سپس آن را به روش کاتلین بازسازی (refactor ) می کنیم. در جاوا، احتمالاً از دنباله ای از دستورات if برای بررسی گزینه ها استفاده می کنید، بنابراین بیایید از همان رویکرد در کاتلین استفاده کنیم.
در کاتلین، شما با استفاده از یک is checkبررسی می کنید که آیا یک متغیر از نوع داده خاصی است یا خیر. اگر در C# برنامه نویسی کرده اید، این نماد باید آشنا باشد. is check مشابه instanceof در جاوا است. اما در جاوا، اگر بررسی کرده اید که یک متغیر دارای نوع داده خاصی است و نیاز به دسترسی به اعضای آن نوع داده دارد، باید یک castواضح و بررسی instanceofاضافه کنید. هنگامی که متغیر اولیه بیش از یک بار استفاده می شود، اغلب نتیجه cast را در یک متغیر جداگانه ذخیره می کنید. در کاتلین، کامپایلر این کار را برای شما انجام می دهد. اگر متغیر را برای نوع داده خاصی بررسی کنید، دیگر نیازی به cast آن ندارید. می توانید از آن به عنوان نوع داده مورد بررسی استفاده کنید. در واقع، کامپایلر، cast را برای شما انجام می دهد و ما آن را یک smart cast می نامیم.
در تابع eval، پس از بررسی اینکه آیا متغیر e دارای نوع داده Num است یا خیر، کامپایلر آن را به عنوان متغیر Num تفسیر می کند. سپس میتوانید به property مقدار Num بدون cast صریح: e.value دسترسی پیدا کنید.
در مورد properties راست و چپ Sum هم همینطور است: شما فقط e.right و e.left را در contextمربوطه می نویسید. در IDE، این مقادیر smart-cast با رنگ پسزمینه تأکید میشوند، بنابراین به راحتی میتوان فهمید که این مقدار از قبل بررسی شده است. شکل 2.5 را ببینید.
اسمارت cast فقط در صورتی کار می کند که متغیری نتواند پس از بررسی (check) تغییر کند. هنگامی که از یک Smart Cast با property یک کلاس استفاده می کنید، مانند این مثال، property باید یک valباشد و نمی تواند یک دسترسی سفارشی داشته باشد. در غیر این صورت، نمیتوان تأیید کرد که هر دسترسی به property همان مقدار را برمیگرداند.
یک cast صریح به نوع داده خاص از طریق کلمه کلیدی as بیان می شود:
val n = e as Num
حال بیایید به نحوه تبدیل تابع eval به سبک کاتلین نگاه کنیم.
دستور if در کاتلین چه فرقی با if در جاوا دارد؟ شما قبلا تفاوت را دیده اید. در ابتدای فصل، عبارت if را دیدید که در زمینه ای (context ) استفاده می شود که جاوا یک عملگر سه تایی دارد: if (a > b) a else b مانند جاوا a > b ? a : b کار می کند؟ در کاتلین، عملگر سه تایی وجود ندارد، زیرا برخلاف جاوا، عبارت if مقداری را برمی گرداند. این بدان معنی است که شما می توانید تابع eval را برای استفاده از سینتکس expression-body بازنویسی کنید، دستور return و پرانتزها را حذف کنید و به جای آن از عبارت if به عنوان بدنه تابع استفاده کنید.
فهرست 2.19 استفاده از if-expressions که مقادیر را برمی گرداند
fun eval(e: Expr): Int = if (e is Num) { e.value } else if (e is Sum) { eval(e.right) + eval(e.left) } else { throw IllegalArgumentException("Unknown expression") } >>> println(eval(Sum(Num(1), Num(2)))) 3
اگر فقط یک عبارت در شاخه if وجود داشته باشد، اکولادها اختیاری هستند. اگر شاخه if یک بلوک باشد، آخرین عبارت در نتیجه برگردانده می شود.
بیایید این کد را حتی بیشتر صیقل دهیم و با استفاده از when بازنویسی کنیم.
که بیان به آن چک کردن مقادیر برای برابری، آن چیزی است که شما پیش از آن دیدم محدود نمی شود. در اینجا از شکل متفاوتی از هنگامی که شاخه ها استفاده می کنید استفاده می کنید و به شما این امکان را می دهد که نوع مقدار آرگومان when را بررسی کنید. درست مانند مثال if در فهرست 2.19 ، بررسی نوع از بازیگران هوشمند استفاده می کند ، بنابراین می توانید بدون ارسال اضافی به اعضای Num و Sum دسترسی داشته باشید .
دو نسخه آخر کاتلین تابع eval را مقایسه کنید و به این فکر کنید که چگونه می توانید به عنوان جایگزینی برای دستورات عبارات if در کد خود نیز اعمال کنید. هنگامی که منطق شاخه پیچیده است، می توانید از یک بلوک به عنوان بدنه شاخه استفاده کنید. بیایید ببینیم این چگونه کار می کند.
هر دو if و when می توانند بلوک هایی را به عنوان شاخه داشته باشند. در این حالت، آخرین expression در بلوک نتیجه است. اگر می خواهید logging را مثلا به تابع اضافه کنید، می توانید این کار را در بلوک انجام دهید و آخرین مقدار را مانند قبل برگردانید.
اکنون می توانید به گزارش های چاپ شده توسط تابع evalWithLoggingنگاه کنید و ترتیب محاسبه را دنبال کنید:
>>> println(evalWithLogging(Sum(Sum(Num(1), Num(2)), Num(4)))) num: 1 num: 2 sum: 1 + 2 num: 4 sum: 3 + 4 7
قانون "آخرین expression در یک بلوک نتیجه و خروجی است" در همه مورد های که می توان از یک بلوک استفاده کرد و نتیجه که انتظار میرود بدست میاید. همانطور که در پایان این فصل خواهید دید، همین قانون برای عبارت try و catch کار می کند، و فصل 5 کاربرد آن را در lambda expressions مورد بحث قرار می دهد. اما همانطور که در بخش 2.2 اشاره کردیم، این قانون برای توابع معمولی صادق نیست. یک تابع می تواند دارای یک بدنه expression باشد که نمی تواند یک بلوک یا یک بدنه بلوکی با return statements صریح در داخل باشد.
شما به وسیله کاتلین با روش های انتخاب اشیاء مناسب در بین اشیاء های دیگر آشنا شده اید. حالا زمان خوبیه تا ببینید چگونه اشیاء تکراری را می توانید بنویسید.
از میان تمام ویژگیهایی که در این فصل بحث شد، تکرار(iteration) در کاتلین احتمالاً شبیهترین ویژگی به جاوا است. حلقه while مشابه همان حلقه در جاوا است، بنابراین اول این بخش فقط به یادآوری مختصری نیاز دارد. حلقه for تنها به یک شکل وجود دارد که معادل حلقه for-each جاوا است. برای نوشتن <item> در <elements> مثل C# نوشته شده است. رایج ترین کاربرد این حلقه تکرار روی مجموعه ها است، درست مثل جاوا. ما بررسی خواهیم کرد که چگونه می تواند سایر سناریوهای حلقه را نیز پوشش دهد.
کاتلین دارای حلقه های while و do-while است و syntax آنها با حلقه های مربوطه در جاوا تفاوتی ندارد:
کاتلین چیز جدیدی به این حلقه های ساده نمی آورد، بنابراین ما معطل نمی شویم. بیایید به بحث در مورد کاربردهای مختلف حلقه forبپردازیم
همانطور که قبلاً اشاره کردیم، در کاتلین هیچ نظم جاوای برای حلقه های تکرار وجود ندارد، جایی که شما یک متغیر را مقداردهی اولیه میکنید، مقدار آن را در هر مرحله از حلقه بهروزرسانی میکنید، و زمانی که مقدار به یک حد مشخص میرسد، از حلقه خارج میشوید. برای جایگزینی رایج ترین موارد استفاده از چنین حلقه هایی، کاتلین از مفاهیم محدوده ها (ranges) استفاده می کند.
یک محدوده(ranges) اساساً فقط یک فاصله بین دو مقدار است، معمولاً اعداد: شروع و پایان. شما آن را با استفاده از اوپراتور (..) می نویسید:
val oneToTen = 1..10
توجه داشته باشید که محدوده ها در کاتلین closed یا inclusive می شوند ، یعنی مقدار دوم همیشه بخشی از محدوده(range) است.
مهمترین ترین کاری که با محدوده های اعداد صحیح می توانید انجام دهید این است که روی همه مقادیر حلقه بزنید. اگر بتوانید روی تمام مقادیر یک محدوده تکرار کنید، چنین محدوده ای را پیشرفت (progression) می نامند.
بیایید از محدوده های اعداد صحیح برای بازی Fizz-Buzzاستفاده کنیم. این یک راه خوب برای زنده ماندن از یک سفر طولانی با ماشین و به یاد آوردن مهارت های تقسیم بندی فراموش شده خود است. بازیکنان به نوبت به صورت افزایشی شمارش می کنند و هر عددی که بر سه بخش پذیر باشد را با کلمه fizz و هر عددی که بر پنج بخش پذیر باشد را با کلمه buzz جایگزین می کنند. اگر عددی مضربی از سه و پنج باشد، می گویید . "FizzBuzz"
لیست زیر پاسخ های مناسبی را برای اعداد 1 تا 100 چاپ می کند. توجه داشته باشید که چگونه شرایط ممکن را با عبارت when بدون آرگومان بررسی می کنید.
فرض کنید بعد از یک ساعت رانندگی از این قوانین خسته شده اید و می خواهید کمی اوضاع را پیچیده کنید. بیایید از 100 به عقب شمارش کنیم و فقط اعداد زوج را در نظر بگیریم.
حالا در حال تکرار روی یک پیشرفت(progression) هستید که دارای یک مرحله است که به آن اجازه می دهد از برخی اعداد عبور کند. همچنین گام منفی باشد، در این صورت پیشروی به جای جلو به عقب می رود. در این مثال، downTo 1 100 یک پیشرفت است که به عقب می رود ( یکی کم میشود). بعد گام را با حفظ جهت، مقدار گام را به 2 تغییر می دهد (در واقع، گام را روی -2 تنظیم می کند یعنی دوتا دوتا کم شود).
همانطور که قبلا ذکر کردیم، syntax .. همیشه محدوده ای ایجاد می کند که شامل نقطه پایانی (مقدار سمت راست .. ) می شود. در بسیاری از موارد، تکرار در محدودههای نیمه بسته، که نقطه پایانی مشخصشده ندارد، راحتتر است. برای ساخت این محدوده از تابع while استفاده کنید. به عنوان مثال، حلقه (x in 0 until size) for معادل for (x in 0..size-1) است، اما این ایده را تا حدودی واضح تر بیان می کند. بعداً، در بخش 3.4.3، در این مثالها درباره سینتکس for downTo, step و until بیشتر خواهید آموخت.
میببینید که چطور کار با محدوده ها(ranges) و پیشرفت ها(progressions) به شما کمک کرد تا با قوانین پیشرفته بازی FizzBuzz کنار بیایید. حالا بیایید به نمونه های دیگری که از حلقه forاستفاده می کنند نگاه کنیم.
ما اشاره کرده ایم که رایج ترین سناریو استفاده از for ... در حلقه تکرار روی یک مجموعه است. این دقیقاً عین جاوا کار می کند، بنابراین ما چیز زیادی در مورد آن نمی گوییم. ببینیم در عوض چگونه میتوانید روی map تکرار کنید.
به عنوان مثال، ما به یک برنامه کوچک نگاه خواهیم کرد که نمایش های باینری را برای کاراکترها چاپ می کند. شما این نمایش های باینری را در یک نقشه (فقط برای واضح شدن اهداف) ذخیره خواهید کرد. کد زیر یک map ایجاد می کند، آن را با نمایش های دودویی برخی از حروف پر می کند و سپس محتوای map را چاپ می کند.
سینتکس (Syntax) .. برای ایجاد یک محدوده نه تنها برای اعداد، بلکه برای کاراکترها نیز کار می کند. در اینجا از آن برای تکرار روی همه کاراکترها از A تا F و از جمله F استفاده میکنید.
فهرست 2.24 نشان میدهد که حلقه for به شما امکان میدهد تا یک عنصر از مجموعهای را که روی آن تکرار میکنید باز کنید (در این مورد، مجموعهای از جفتهای key/value در map ). شما نتیجه باز کردن بسته بندی را در دو متغیر جداگانه ذخیره می کنید: key حروف را دریافت می کند و value باینری را دریافت می کند. بعداً، در بخش 7.4.1، درباره این syntax باز کردن بستهبندی اطلاعات بیشتری خواهید یافت.
یکی دیگر از ترفندهای خوب مورد استفاده در فهرست 2.24، syntax مختصر برای دریافت و به روز رسانی values یک map توسط key است. به جای فراخوانی get و put، می توانید از map[key] برای خواندن مقادیر و map[key] = value برای تنظیم آنها استفاده کنید. کد
binaryReps[c] = binary
همین کد به زبان جاوا:
binaryReps.put(c, binary)
خروجی مشابه زیر است (ما آن را به جای یک ستون در دو ستون مرتب کرده ایم):
A = 1000001 E = 1000101 D = 1000100 C = 1000011 B = 1000010 F = 1000110
میتوانید از همان syntax باز کردن برای تکرار روی یک مجموعه و در حین نگهداری مسیر از بخش رایج index of استفاده کنید. برای ذخیره کردن ایندکس و افزایش آنبصورت دستی نیازی به ایجاد یک متغیر جداگانه ندارید
این کد اونی که انتظار دارید چاپ می کنه:
0: 10 1: 11 2: 1001
در فصل بعدی مکان WithIndex را بررسی خواهیم کرد.
شما دیده اید که چگونه می توانید از کلمه کلیدی in برای تکرار در یک محدوده(range) یا مجموعه استفاده کنید.
همچنین می توانید از in برای بررسی اینکه آیا یک مقدار متعلق به محدوده یا مجموعه است استفاده کنید..
شما از عملگر in برای بررسی اینکه آیا یک مقدار در یک محدوده است یا مخالف آن، !in استفاده می کنید تا بررسی کنید که آیا یک مقدار در یک محدوده نیست. در اینجا نحوه استفاده از in برای بررسی تعلق یک کاراکتر به طیفی از کاراکترها آمده است.
این تکنیک برای بررسی اینکه آیا یک کاراکتر یک حرف است، ساده به نظر می رسد. در زیر پوشش، هیچ اتفاق سختی نمیافتد: بررسی میکنید که کد کاراکتر جایی بین کد حرف اول و کد حرف آخر باشد. اما این منطق به طور خلاصه در اجرای کلاس های محدوده(range) در کتابخانه استاندارد پنهان است:
عملگرهای in و !in نیز در عبارات When کار می کنند.
محدوده ها نیز به کاراکترها محدود نمی شوند. اگر کلاسی دارید که از مقایسه نمونه ها پشتیبانی می کند (با پیاده سازی اینترفیس java.lang.Comparable)، می توانید محدوده هایی از objects آن نوع داده ایجاد کنید. اگر چنین محدوده ای دارید، نمی توانید همه اشیاء را در محدوده بکنید. در مورد آن فکر کنید: می توانید، برای مثال، تمام رشته های بین "جاوا" و "کاتلین" را برشمارید؟ نه، شما نمی توانید. اما همچنان می توانید با استفاده از عملگر inبررسی کنید که آیا شی دیگری به محدوده تعلق دارد یا خیر:
توجه : رشته ها در اینجا بر اساس حروف الفبا مقایسه می شوند، زیرا کلاس String اینگونه Comparable interface را پیاده سازی می کند.
همین بررسی با مجموعه ها نیز کار می کند:
بعداً، در بخش 7.3.2، نحوه استفاده از محدودهها و پیشرفتها با انواع دادههای خود را خواهید دید و به طور کلی با چه اشیایی میتوانید از in برای بررسی استفاده کنید.
یک گروه دیگر از عبارات جاوا وجود دارد که می خواهیم در این فصل به آنها نگاه کنیم: عباراتی برای برخورد با استثناها (exceptions).
مدیریت استثناها در کاتلین مشابه روشی است که در جاوا و بسیاری از زبان های دیگر انجام می شود. یک تابع می تواند به صورت عادی کامل شود یا در صورت بروز خطا، یک استثنا ایجاد کند. فراخوان تابع (جایی که تابع را صدا و استفاده میکنیم) می تواند این استثنا را بگیرد و آن را پردازش کند. اگر اینطور نباشد، استثنا دوباره در پشته(stack) قرار می گیرد.
شکل اصلی دستورات رسیدگی به استثنا در کاتلین شبیه به جاوا است. شما یک استثنا را به شیوه ای بدون غافلگیری مطرح می کنید:
if (percentage !in 0..100) { throw IllegalArgumentException( "A percentage value must be between 0 and 100: $percentage") }
مانند سایر کلاسها، لازم نیست از کلمه کلیدی new برای ایجاد یک نمونه استثنا استفاده کنید.
بر خلاف جاوا، در کاتلین ساختار throw یک عبارت است و می تواند به عنوان بخشی از عبارات دیگر استفاده شود:
در این مثال، اگر شرط برآورده شود، برنامه به درستی رفتار می کند و متغیر درصد (percentage) با عدد(number) مقدار دهی اولیه می شود. در غیر این صورت، یک استثنا ایجاد می شود و متغیر مقداردهی اولیه نمی شود. ما جزئیات فنی throw را به عنوان بخشی از عبارات دیگر در بخش 6.2.6 مورد بحث قرار خواهیم داد.
همانطور که در جاوا، شما از ساختار try با دستورات catch و finally برای رسیدگی به استثناها استفاده می کنید. میتوانید آن را در فهرست زیر ببینید، که یک خط از فایل داده شده را میخواند، سعی میکند آن را به عنوان یک عدد تجزیه کند، و اگر که شماره خط معتبر نباشد یکی از اعداد یا null برمیگرداند
بزرگترین تفاوت کاتلین با جاوا این است که عبارت throws در کد وجود ندارد: اگر این تابع را در جاوا بنویسید، صراحتاً throws IOException را بعد از اعلام تابع مینویسید. شما باید این کار را انجام دهید زیرا IOException یک استثناء بررسی شده است. در جاوا، این یک استثنا است که باید به طور صریح رسیدگی شود. شما باید تمام استثناهای بررسی شده را که تابع شما می تواند پرتاب کند، اعلام کنید، و اگر تابع دیگری را فراخوانی کنید، باید استثناهای بررسی شده آن را مدیریت کنید یا اعلام کنید که تابع شما می تواند آنها را نیز پرتاب کند.
درست مانند بسیاری دیگر از زبانهای مدرن JVM، کاتلین بین استثناهای چک شده و چک نشده تفاوتی قائل نمیشود. شما استثناهای ایجاد شده توسط یک تابع را مشخص نمی کنید، و ممکن است هر استثنایی را مدیریت کنید یا نکنید. این تصمیم طراحی بر اساس تمرین استفاده از استثناهای بررسی شده در جاوا است. تجربه نشان داده است که قوانین جاوا اغلب به کدهای بیمعنی زیادی برای بازگرداندن یا نادیده گرفتن استثناها نیاز دارند، و قوانین به طور مداوم از شما در برابر خطاهایی که ممکن است رخ دهد محافظت نمیکنند.
به عنوان مثال، در فهرست 2.27، NumberFormatException یک استثنا نیست. بنابراین، کامپایلر جاوا شما را مجبور به گرفتن آن نمی کند و به راحتی می توانید مشاهده کنید که این استثنا در زمان اجرا اتفاق می افتد. این مایه تاسف است، زیرا دادههای ورودی نامعتبر یک وضعیت رایج است و باید با ظرافت با آنها رفتار شود. در همان زمان، متد BufferedReader.close میتواند یک IOException ایجاد کند، که یک استثنا بررسی شده است و باید مدیریت شود. اگر بستن یک جریان با شکست مواجه شود، اکثر برنامهها نمیتوانند هیچ اقدام معنیداری انجام دهند، بنابراین کد مورد نیاز برای گرفتن استثنا از متد close ، boilerplate است.
در مورد جاوا7 try-with-resources چطور؟ کاتلین دستور خاصی برای این کار ندارد. به عنوان یک تابع کتابخانه پیاده سازی شده است. در بخش 8.2.5، خواهید دید که چگونه این امکان وجود دارد.
برای مشاهده تفاوت مهم دیگر بین جاوا و کاتلین، اجازه دهید مثال را کمی اصلاح کنیم. بیایید بخش finally را حذف کنیم (زیرا قبلاً نحوه عملکرد آن را دیدهاید) و مقداری کد برای چاپ شمارهای که از فایل خواندهاید اضافه میکنیم.
کلمه کلیدی try در کاتلین، درست مانند if و when، یک expression را معرفی می کند و می توانید مقدار آن را به یک متغیر اختصاص دهید. برخلاف if، شما همیشه باید بدنه عبارت را در اکولاد قرار دهید. درست مانند سایر دستورات، اگر بدنه شامل چند expressions باشد، مقدار عبارت try به عنوان مجموع مقدار آخرین expression است.
این مثال یک عبارت بازگشتی را در بلوک catchقرار می دهد، بنابراین اجرای تابع بعد از بلوک catch ادامه نمی یابد. اگر می خواهید اجرا را ادامه دهید، شرط کش catch clause نیز باید مقداری داشته باشد که مقدار آخرین عبارت در آن خواهد بود. در اینجا نحوه کار را میبینیم.
اگر اجرای یک بلوک کد try به طور معمول رفتار کند، آخرین عبارت در بلوک نتیجه است. اگر یک استثنا گرفته شود، آخرین عبارت در یک بلوک catchمربوطه نتیجه است. در فهرست 2.29، اگر NumberFormatException گرفته شود، مقدار نتیجه صفر است.
در این مرحله، اگر حوصله ندارید، میتوانید برنامههایی را در کاتلین بنویسید که شبیه به نحوه کدنویسی در جاوا است. همانطور که این کتاب را می خوانید، یاد خواهید گرفت که چگونه روش های معمول تفکر خود را تغییر دهید و از تمام قدرت زبان جدید استفاده کنید.
1.6 خلاصه
بخش سوم از فصل اول را در اینجا مشاهده کنید