الفرق بين المراجعتين ل"Kotlin/classes"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(إضافة الفقرتين الأخيرتين)
سطر 91: سطر 91:
  
 
== الوراثة (Inheritance) ==
 
== الوراثة (Inheritance) ==
 +
تشترك كل الأصناف في لغة Kotlin بالصنف <code>Any</code> باعتباره الصنفَ الأعلى (superclass) الافتراضيّ لأي صنفٍ لم يُصرَّح عن صنفٍ أعلى له، مثل:<syntaxhighlight>
 +
class Example // وراثة ضمنيّة من الصنف Any
 +
</syntaxhighlight>ملاحظة: إن الصنف <code>Any</code> ليس من <code>java.lang.Object;‎</code> إذ لا يحتوي إلا على الدوال الثلاثة: <code>equals()‎</code> و <code>hashCode()‎</code> و <code>toString()</code>‎.
 +
 +
وللتصريح عن الصنف الأعلى (superclass) بشكلٍ واضحٍ يُوضَع النوع بعد الرمز <code>:</code> في ترويسة الصنف (class header)، مثل:<syntaxhighlight lang="kotlin">
 +
open class Base(p: Int)
 +
 +
class Derived(p: Int) : Base(p)
 +
</syntaxhighlight>تعبِّر الكلمة المفتاحيّة <code>open</code> (قبل اسم الصنف) عن المعنى المعاكس للكلمة المفتاحية <code>final</code> في لغة Java، إذ يُسمَح (بوجود <code>open</code>) بعملية الوراثة من هذا الصنف لأنّ الحالة الافتراضيّة للأصناف في لغة Kotlin هي final.
  
 
=== إعادة تعريف التوابع () ===
 
=== إعادة تعريف التوابع () ===

مراجعة 17:05، 9 مارس 2018

الأصناف (Classes)

تُستخدم الكلمة المفتاحيّة class للتصريح (declaration) عن الصنف بالصيغة الآتية: (اسم الصنف Invoice)

class Invoice {
}

ويحتوي التصريح على اسم الصنف (class name) وترويسة الصنف (class header) (والتي تُحدِّد متحولات النوع والباني الأساسيّ و.. إلخ.) وبُنية الصنف (class body) محاطةً بالقوسين {}، وإن كلًا من ترويسة الصنف وبُنيته اختياريتان؛ فإذا كان الصنف خاليًا لا حاجة للأقواس، مثل:

class Empty

الباني (Constructor)

يوجد لكلّ صنف في لغة Kotlin بانٍ رئيسيّ (primary) واحدٌ وبانٍ -أو أكثر- ثانويّ (secondary)، إذ يُعدُّ الباني الرئيسيّ جزءًا من ترويسة الصنف (header) حيث يُضاف بعد اسم الصنف مباشرةً (وقد تُضاف له المتحولات (parameters))، مثل:

class Person constructor(firstName: String) {
}

وإن لم يكن للباني الرئيسيّ حاشيةٌ (annotation) أو مُحدِّد وصول (visibility modifier) فتُحذَف حينئذِ الكلمة المفتاحيّة constructor مثل:

class Person(firstName: String) {
}

وكما يُلاحظ أن الباني الرئيسيّ لا يحتوي على أيّة شيفرةٍ لأنّ شيفرة التهيئة الأولية (initialization code) تُكتب في أجزاء خاصّةٍ للتهيئة (initializer blocks) والتي تُسبَق بالكلمة المفتاحيّة init، إذ تُنفَّذُ هذه الأجزاء -عند عملية الإنشاء من هذا الصنف- بنفس الترتيب الموجودة فيه ضمن الصنف متداخلةً مع عمليات تهيئة الخاصّيّات (property initializers)، مثل:

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)

    init {
        println("First initializer block that prints ${name}")
    }

    val secondProperty = "Second property: ${name.length}".also(::println)

    init {
        println("Second initializer block that prints ${name.length}")
    }
}

ويٌسمَح باستخدام متحوِّلات (parameters) الباني الأساسيّ في أجزاء التهيئة كما يُمكِن استخدامها أيضًا في تهيئة الخاصّيّات (properties) المُصرَّح عنها في بُنية الصنف (class body)، مثل:

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

وتدعم Kotlin صيغةً مختصرةً للتصريح عن الخاصّيّات وتهيئتها الأولية في الباني الأساسيّ، وهي:

class Person(val firstName: String, val lastName: String, var age: Int) {
    // ...
}

وكما هو الحال في الخاصّيّات بشكلٍ عامّ، قد تُعرَّف الخاصّيّات في الباني الأساسيّ كخاصيّات متغيّرة (mutable)‏ var أو خاصّيات للقراءة فقط (read-only)‏ val. وفي حال وجود حاشيةٍ (annotation) أو مُحدِّد وصولٍ (visibility modifier) للباني فلا بُدَّ من وجود الكلمة المفتاحيّة constructor إذ تُوضع الحاشية أو المُحدِّدات قبلها بالصيغة:

class Customer public @Inject constructor(name: String) { ... }

المزيد عن مُحدِّدات الوصول (visibility modifiers).

الباني الثانويّ (Secondary Constructors)

يُصرَّح عن الباني الثانويّ في الصنف (class) بالبدء به عبر الكلمة المفتاحيّة constructor، مثل:

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

فإذا احتوى الصنف بانيًا رئيسيًا (primary constructor) فإن كلَّ بانٍ ثانويّ سيُفضي في النهاية إليه إما مباشرةً أو بطريقةٍ غير مباشرة عبر بانٍ ثانويّ آخر، وبكلا الحالتين يكون الوصول للباني الرئيسيّ من خلال الكلمة المفتاحيّة this، مثل:

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

ويُلاحظ بأن الشيفرة في أجزاء التهيئة (initializer blocks) تصبح جزءًا من الباني الأساسيّ حيث يكون الانتقال للباني الرئيسيّ بمثابة التعليمة الأولى في الباني الثانويّ، وبالتالي فإن كلّ الشيفرات الموجودة في أجزاء التهيئة ستُنفَّذ قبل الباني الثانويّ، حتى وإن لم يكن هناك بانٍ رئيسيّ للصنف فإن هذا الانتقال سيكون ضمنيًا (implicit) وستُنفَّذ أجزاء التهيئة أيضًا، مثل:

class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor")
    }
}

أما إذا لم يحتوِ الصنف (غير المُجرَّد non-abstract) على أي تصريحٍ لبانٍ (رئيسيّ أو ثانويّ) فسيُولَّد تلقائيًا بانٍ افتراضيّ بدون متحولات (arguments)، ويكون مُحدِّد الوصول له من النوع العامّ (public)، فإذا أردت ألا يكون عامًّا فعليك بإنشاء باني رئيسيٍّ فارغٍ وبالمُحدِّد المناسب، مثل:

class DontCreateMe private constructor () {
}

ويُلاحظ في بيئة JVM أنّه إن كان لكل متحوِّلات (parameters) الباني قيمٌ افتراضيّةٌ فإن المُترجم (compiler) سيُولِّد بانيًا إضافيًا بدون متحولات (parameterless)، والذي سيستخدم القيم الافتراضيّة، وهذا ما يجعل لغة Kotlin سهلة الاستخدام مع المكتبات (libraries) مثل Jackson أو JPA اللتان تنشِئان الكائنات باستخدام البواني بدون المتحولات. مثل:

class Customer(val customerName: String = "")

إنشاء كائنات (Instances) من الصنف

لإنشاء كائنٍ من أحد الأصناف يُستدَعى الباني (constructor) فيه كما لو أنّه دالةٌ عاديّة، مثل:

val invoice = Invoice()

val customer = Customer("Joe Smith")

إذ لا تعتمد لغة Kotlin على وجود الكلمة المفتاحيّة new كما هو الحال في لغة Java.

أمّا لإنشاء الكائنات من الأصناف المتداخلة (nested) أو الأصناف الداخليّة (inner) أو الداخليّة المجهولة (anonymous inner) راجع الأصناف المتداخلة.

عناصر الصنف (Class Members)

قد يحتوي الصنف على:

الوراثة (Inheritance)

تشترك كل الأصناف في لغة Kotlin بالصنف Any باعتباره الصنفَ الأعلى (superclass) الافتراضيّ لأي صنفٍ لم يُصرَّح عن صنفٍ أعلى له، مثل:

class Example // وراثة ضمنيّة من الصنف Any

ملاحظة: إن الصنف Any ليس من java.lang.Object;‎ إذ لا يحتوي إلا على الدوال الثلاثة: equals()‎ و hashCode()‎ و toString()‎. وللتصريح عن الصنف الأعلى (superclass) بشكلٍ واضحٍ يُوضَع النوع بعد الرمز : في ترويسة الصنف (class header)، مثل:

open class Base(p: Int)

class Derived(p: Int) : Base(p)

تعبِّر الكلمة المفتاحيّة open (قبل اسم الصنف) عن المعنى المعاكس للكلمة المفتاحية final في لغة Java، إذ يُسمَح (بوجود open) بعملية الوراثة من هذا الصنف لأنّ الحالة الافتراضيّة للأصناف في لغة Kotlin هي final.

إعادة تعريف التوابع ()

إعادة تعريف الخاصيات ()

ترتيب تهيئة الصنف المشتق

استدعاء إعادة التعريف من الصنف الأعلى

إعادة تعريف القواعد

الأصناف المُجرَّدة (Abstract Classes)

قد يُصرَّح عن الصنف أو بعضٍ من عناصره (members) بالكلمة المفتاحيّة abstract (مُجرَّد) بحيث تكون هذه العناصر دون تعريفٍ للاستخدام (implementation) ضمن الصنف الموجودة فيه، ولا حاجة لإضافة الكلمة المفتاحيّة open للصنف أو العناصر المجرَّدة فهي موجودةٌ بالحالة الافتراضيّة.

وبالإمكان إعادة تعريف (override) العنصر المفتوح غير المُجرَّد في عنصرٍ آخر مُجرَّد، مثل:

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

الكائنات المرافقة (Companion Objects)

لا وجود للتوابع الستاتيكيّة (static methods) في Kotlin -على عكس لغتيّ البرمجة Java وC#‎- ويُستعاض عنها بالدوال على مستوى الحزمة (package-level functions).

وعند الحاجة إلى كتابة دالةٍ قابلةٍ للاستدعاء دون إنشاء كائنٍ (instance) من الصنف ولكن لها صلاحيّة الوصول (access) إلى ما داخل الصنف (للتابع المُنتِج (factory method) مثلًا) فيُمكن جعلها كعنصرٍ (member) من تصريح الكائن نفسه داخل ذلك الصنف.

وبشكل أكثر تفصيلًا؛ إذا صُرِّح عن الكائن المرافق داخل الصنف فيمكن حينئذٍ استدعاء عناصره بنفس صيغة استدعاء التوابع الستاتيكيّة في لغتيّ Java وC#‎ بالاعتماد على اسم الصنف فقط.

مصادر