کلاس خالی میتونه {} نداشته باشه.
class MyClass
متد سازنده یا constructor کلاس رو میشه توی هدر نوشت
class MyClass constructor( … ) { … }
اگه بخوایم constructor کلاس از بیرون در دسترس نباشد باید آن را private کنیم
class MyClass private constructor( … ) { … }
اگه constructor ما annotations یا visibility modifiers نداشته باشه میتونیم اون رو ننویسیم.
class MyClass( … ) { … }
اگه کلاس ما توی ورودی چیزی لازم نداشته باشه بگیره میشه () رو نزاریم
class MyClass { … }
وقتی از constructor توی هدر استفاده میکنیم. باید کدی که میخوایم در ابتدای ساخته شدن کلاس اجرا بشه رو توی یک {} در بدنیه کلاس بنویسیم. فقط دقت کنید که قبلش باید عبارت init رو بنویسیم و ما میتونیم چند init توی بدنه کلاس داشته باشیم که به ترتیب از بالا به پایین اجرا میشن.
class MyClass { init { // first } init { // second } }
اگه قبل از ورودیهای constructor از val یا var استفاده کنیم اونها تبدیل به property میشن. در غیر این صورت فقط به صورت یک متغیر در بدنه init در دسترس هستند.
ورودیهای constructor میتونن مقدار پیش فرض داشته باشن.
در constructor از trailing comma پشتیبانی میکنه.
اگه بخوایم کلاسمون چند تا constructor داشته باشه میتونیم متدش رو بنویسیم. فقط توجه داشته باشید که اگه تو هدر constructor داشته باشیم باید بعد از متد constructor حتما this رو صدا بزنیم و متد constructor هدر رو پیاده سازی کنیم.
class MyClass( … ) { init { … } constructor( … ) : this( … ) { ... } constructor( …, ... ) : this( … ) { ... } }
بلاک init قبل از constructor ها اجرا میشود.
کلاس ها با : از کلاس های دیگه ارث میبرن یا اینترفیس ها رو پیاده سازی میکنن.
برای توسعه کلاس یا باید از modifier های open یا abstract استفاده کنیم. کلاس ها توی کاتلین به صورت پیش فرض final هستند یعنی نمیشه اونها رو توسعه داد. توجه کنید که از کلاس abstract نمیشه یک نمونه ساخت و فقط باید اون رو به وسیله کلاس های دیگه توسعه داد.
بعد از توسعه یک کلاس، اگه کلاس پدر ورودی داشت یا باید توی هدر argument ها رو ارسال کنیم یا باید در متد سازنده super رو صدا بزنیم.
open class MyParent( … ) { … } class MyClass( … ) : MyParent( … ) { … }
class MyClass : MyParent { constructor( … ) : super( … ) { … } }
برای اینکه کلاس فرزند بتونه function ها یا value ها رو بازنویسی کنه باید قبل از تعریف اونها از کلمه open استفاده کنیم.
open val MyValue = “value” open fun MyFunction( … ) { … }
زمانی که ما یک کلاس رو توسعه میدیم و از subclass اون یک نمونه میسازیم قبل از ساخته شدن اون، یک نمونه از superclass ساخته میشه. که این به این معنی هستش که متد constructor یا بلاک init مربوط به superclass زودتر اجرا میشن. به همین دلیل، متغیرهایی که ما توی subclass مقداردهی یا بازنویسی کردیم هنوز داده مورد نظر ما رو ندارن. به همین دلیل استفاده از متغیرهای open توی متد constructor یا بلاک init اشتباه هستش.
توی یک کلاس برای دسترسی به superclass از عبارت super باید استفاده کنیم.
وقتی از کلاس های تو در تو استفاده میکنیم میتونیم برای اشاره درست به superclass مورد نظر از @ استفاده کنیم.
super@Superclass.method()
وقتی یک subclass بیشتر از یک superclass دارد برای اشاره به superclass مورد نظر از <> باید استفاده کنیم.
super<Superclass>.method()
در زمان پیاده سازی یک اینترفیس میتونیم متغیرها رو توی هدر override کنیم.
interface MyInterface { val myValue: String } class MyClass( override val myValue: String ) : MyInterface {}
اگه توابع هم ساختار توی کلاس پدر و یا اینترفیس ها وجود داشته باشه هیچ مشکلی پیش نمیاد و فقط لازمه یکی از اونها رو بنویسیم و با استفاده از <> میتونیم به superclass مورد نظر دسترسی داشته باشیم.
برای پیاده سازی interface ها یا abstract class ها میتونیم از object ها هم استفاده کنیم.
interface MyInterface { fun myFunction() } val myObject = object : MyInterface { override fun myFunction() {} }
اگه یک اینترفیس فقط یک متد داشته باشد برای پیاده سازی آن نیازی به استفاده از object نداریم. که به آن SAM که مخفف Single Abstract Method است هم میگن.
interface MyInterface { fun myFunction() } val mySAM = MyInterface {}
توی اینترفیسها میشه متدها رو هم پیاده سازی کرد و اینجوری لازم نیست اون متد پیاده سازی شده رو توی subclass پیاده سازی کرد.
با typealias ما میتونیم برای یک متغیر با اسم بزرگ و سخت یک اسم راحت انتخاب کنیم. فقط دقت کنید که اونها رو فقط میشه به صورت top level تعریف کرد.
typealias MyList = MutableList<MutableList<String>> val myValue : MyList? = mutableListOf()
به تعریف کلاس های توی هم میگن inner class و به صورت پیشفرض static هستند.
کلاس ها نمیتونن متغیر های private داخل inner class های خودشون رو ببین.
به متدها و متغیرهایی که بیرون کلاس ها نوشته میشن top level میگن
به متدها و متغیرهایی که توی کلاس های نوشته میشن member میگن
به متدها و متغیرها و کلاسهایی که توی متدها نوشته میشن local میگن.
برای استفاده از متدها و متغیرهای top level کلاس های دیگه باید package آنها را import کنیم.
متدها و متغیرها و کلاسهای local نمیتونن modifier داشته باشن.
تنها modifier متفاوت کاتلین internal هستش که شی رو توی ماژول در دسترس قرار میده.
تو کاتلین data class ها اشیای بهتری برای نگهداری داده های هستند و در اونها تغییراتی برای افزایش قابلیتهاشون بوجود اومده.
متد toString توی data class ها اسم کلاس به همراه اسم متغیرها و مقدار اونها رو چاپ میکنه در حالی که توی کلاس های معمولی اسم و رفرنس کلاس رو چاپ میکنه.
متد copy به اونها اضافه شده که میشه با اون یک کپی از شی بگیریم و البته میتونیم هر پارامتری رو هم که خواستیم به این متد بفرستیم و اون رو توی شی جدید تغییر بدیم ولی باید اسم اون رو هم ذکر کنیم.
به ازای هر متغیر در data class ها اونها به صورت خودکار یک متد component میسازن و اون ها رو به ترتیب از ۱ شماره میزنن که هر کدوم از این متد ها به داده یکی از متغیر ها متصله بهش میگن componentN.
یک قابلیت داریم به اسم destructuring declarations که میتونه اشیا رو بشکنه به متغیر ها
data class MyDataClass(x: String, y: Int) val myData = MyDataClass(“”, 0) val (name, age) = myData
متد equals که توی کلاس Any پیاده سازی شده که superclass تمام اشیاست به رفرنس شی اشاره میکنه ولی تو data class با استفاده از متد hashCode که اون هم به صورت پیشفرض تو data class ها پیاده سازی شده به داده شی اشاره میکنه و اینجوری میتونه دوتا شی با رفرنس متفاوت ولی با داده یکسان رو با هم برابر قرار بده.
دوتا شی Pair و Triple که به صورت پیشفرض تو کاتلین وجود دارن data class هستن.
کاتلین یک شی جالب داره به اسم object. میشه تو اونها متغیر، ثابت، ثابت compile time، متد، کلاس و حتی object تعریف کنیم. ولی اونها رو فقط میشه به صورت top level ایجاد کرد.
برای دسترسی به اونها لازم نیست یک نمونه از اونها داشته باشیم. این به این معنی نیست که نمیشه از اونها نمونه ساخت.
در تعریف Object شی به صورت single در پروژه ایجاد میشن و یکتا هستن. تابع سازنده ندارن ولی بلاک init دارن و از قوانین ارث بری پیروی میکنن. این اشیا به صورت lazy هستند و فقط وقتی برای اولین بار صدا زده میشن ساخته میشن.
object MyObject { … } object MyObject : MyInterface { … }
از Object expressions برای inheriting anonymous و anonymous objects و anonymous return value استفاده میشن. در این شرایط به دادههای scope خود دسترسی دارند. این اشیا بلافاصله وقتی ازشون استفاده میشه ساخته و اجرا میشن.
Anonymous objects ⇒ val myobject = object { ... } Inheriting anonymous ⇒ window.addMouseListener(object : MouseAdapter() { … }) Anonymous return value ⇒ fun myFunction() = object { … }
تو کاتلین ما static نداریم یعنی به اشیای یک کلاس ( متد و متغیر ) نمیشه بدون ایجاد یک شی از اون کلاس دسترسی داشت ولی. همینطور ما نمیتونیم که object توی کلاس بسازیم. ولی با استفاده از عبارت companion میتونیم یک object توی کلاس ایجاد کنیم که رفتاری مشابه static داره یعنی بدون نیاز به ایجاد یک نمونه از اون شی میتونیم به متدها و متغیرهاش دسترسی داشته باشیم.
از درون companion object میشه به اطلاعات private دسترسی داشت، همچنین این object میتونه نام نداشته باشه.
class MyClass { companion object { … } }
این اشیا از قوانین اشیای static در جاوا پیروی میکنن و زمانی که کلاس اصلی داره بارگذاری میشه ساخته میشن.
با هدف جلوگیری از استفاده از ثابتهای رشته ای که احتمال خطا توی اونها خیلی زیاد بوده و دامنه اونها هم مشخص نبوده enum ها بوجود اومدن.
توی کاتلین enum class ها بین class و object هستند. کلاس اصلی یک constructor داره که تمام اشیا باید اون رو پیاده سازی کنن. همچنین میشه متدهای abstract و یا open یا متغیر تو کلاس اصلی تعریف کنیم. که همه اشیا object هایی هستند که از کلاس اصلی ارث برده اند. اشیای درون enum ها همه object هستند و به صورت یکتا در پروژه ایجاد میشن. و این بزرگترین نقطه ضعف enum هستش. اگه داده ای در یکی از اونها تغییر کنه داده در کل پروژه تغییر میکنه.
توجه کنید enum ها میتونن اینترفیس ها رو پیاده سازی کنن ولی کلاس ها رو نه!
برای جدا کردن object های enum از متغیرها و متدها باید در انتهای اونها ; بزاریم.
enum class MyEnum(value:Int) : MyInterface { FIRST(1) { override fun myFunction() { ... } }, SECOND(2) { override fun myFunction() { … } }, THIRD(3) { override fun myFunction() { … } } }
یک مشکل بزرگ enum داشت یکتا بودنش در پروژه بود. برای رفع این مشکل ما sealed ها رو داریم (وابسته به نیاز ما میتونن کلاس یا اینترفیس باشن) که اشیای اونها یکتا نیست.
اشیایی (subclassهایی) که یک شی sealed رو توسعه میدن باید میتونن به صورت تودرتو باشن یا توی همون فایلی باشن که شی اصلی هستش و یا توی یک پکیج.
اشیای sealed رو object ها خیلی راحت توسعه میدن. ولی باید توجه داشته باشیم که اگه از object ها توی sealed کلاس ها استفاده کنیم رفتار اونها کاملا شبیه enum ها میشه.
اگه یک class معمولی بخواد یک sealed رو توسعه بده باید حتما متدهای equal و hashCode رو بازنویسی کنه.
اشیای sealed میتونن از قوانین ارث بری پیروی کنن. اگه یک sealed class از یک اینترفیس ارث بری کند تمام اشیا باید آن را پیاده سازی کنن مگر اینکه خود کلاس اصلی آن را پیاده سازی کرده باشد.
توی کاتلین sealed class ها مثل کلاس های abstract هستند و الزامی به پیاده سازی متد ها یا متغیرهای abstract را ندارند.
توی کاتلین Nested که میشه همون تو در تو کاملا پشتیبانی میشه و ما میتونیم کلاس ها و اینترفیس ها رو به هر ترتیبی که دوست داریم توی هم ایجاد کنیم.
برای دسترسی به کلاس های تو در تو نیازی به داشت یک رفرنس از شی پدر نیست. اونها به صورت پیش فرض static هستند
حالا اگه ما قبل از کلاس ها یا اینترفیس های توی یک کلاس عبارت inner رو بزاریم حتما باید یک نمونه از کلاس پدر رو برای دسترسی به اونها داشته باشیم.
دقت کنید anonymous inner classes همون inheriting anonymous تو object expressions هستش فقط اسمش رو عوض کردن.
یادآوری:
داده primitive یا داده اولیه داده های هستن مثل int و float و byte و char و boolean و double و …
کلاسهای Wrapper کلاس هایی هستند که داده های primitive رو به object تبدیل میکنند و بالعکس.
از نسخه ۵ جاوا به بعد ی قابلیتی به اسم autoboxing و unboxing اضافه شد که wrapper ها رو انجام میداد. که autoboxing یا boxing میشه تبدیل داده primitive به object و unboxing میشه تبدیل داده object به primitive.
یک مفهوم دیگه برای wrapper ها در زمان کار کردن با کلاس های final هستش. چون ما اونها رو نمیتونیم توسعه بدیم میتونیم با استفاده از یک wrapper این کار رو انجام بدیم. به این صورت که یک شی از اون توی کلاسمون میسازیم به صورت private و متدهایی هم نام متدهای شی اصلی درست میکنیم و پیاده سازی های خودمون رو ایجاد میکنیم.
گاهی ما نیاز داریم که یک object از داده primitive متناسب با ساختار برنامه خودمون ایجاد کنیم. این کار از نظر پردازشی خیلی کار پرهزینه هستش به قولی runtime overhead داره که به خاطر additional heap allocation هستش. برای حل این مشکل از inline class ها استفاده میکنیم که منطقش شبیه inline function هاست.
نکته: از نظر نوشتاری inline class به value class تغییر کرده ولی هنوز بهشون میگن inline class. و باید از انوتیشن JvmInline استفاده کنیم.
باید حتما و فقط یک ورودی در constructor بگیرند که داده primitive باشه.
از متغیر و متد و بلاک init هم پشتیبانی میکنن. البته متغیرها از backing field پشتیبانی نمیکنن و نمیتونیم lateinit var داشته باشیم.
میتونن اینترفیس ها رو پیاده سازی کنن ولی نمیتونن تو سلسله مراتب وراثت باشن به همین ترتیب کسی نمیتونه اونها رو توسعه بده و به نوعی final هستند.
تو کاتلین inline class ها وقتی کامپایل میشن به داده داخلیشون تبدیل میشن که بهش میگن underlying type. اگه موقع استفاده از اونها دقت نکنیم ممکنه خطا های مبهمی بوجود بیاریم.
@JvmInline value class UInt(val x: Int) fun compute(x: Int) { } fun compute(x: UInt) { } // Represented as 'public final void compute(int x)' on the JVM
تو مثال بالا هر دو متد ما در JVM به یک تابع مشابه تبدیل میشن. که خوب برای رفع این مشکلی هم hasecode شی UInt به انتهای نام متد اضافه میشه. علاوه بر این خودمون هم میتونیم با JvmName به این تابع نامی که میخوایم رو اختصاص بدیم.
شاید فکر کنید که این خیلی شبیه type alias هستش ولی با هم یک تفاوت بزرگ دارن. type alias فقط نام اون رو برای ما در زمان برنامه نویسی تغییر میده در حالی که inline class واقعا یک نوع جدید ایجاد میکنه.