الفرق بين المراجعتين لصفحة: «Kotlin/properties»
طلا ملخص تعديل |
تعديل مصطلح خاطئ |
||
سطر 47: | سطر 47: | ||
وبدءًا من الإصدار 1.1 يمكن حذف نوع الخاصّيّة إذا أمكن تحديده من خلال getter مثل:<syntaxhighlight lang="kotlin"> | وبدءًا من الإصدار 1.1 يمكن حذف نوع الخاصّيّة إذا أمكن تحديده من خلال getter مثل:<syntaxhighlight lang="kotlin"> | ||
val isEmpty get() = this.size == 0 // لها النوع Boolean | val isEmpty get() = this.size == 0 // لها النوع Boolean | ||
</syntaxhighlight>ويمكن في بعض الأحيان تعريفُ (define) أيّ من دوال الوصول (accessors) بدون تعريف بُنيتها (body) وذلك عند الحاجة لتغيير مرئيّة الوصول لها (visibility) أو إضافة | </syntaxhighlight>ويمكن في بعض الأحيان تعريفُ (define) أيّ من دوال الوصول (accessors) بدون تعريف بُنيتها (body) وذلك عند الحاجة لتغيير مرئيّة الوصول لها (visibility) أو إضافة التوصيف (annotation) دون تغيير تعريف الاستخدام (implementation) الافتراضيّ، كما هو الحال في الشيفرة الآتية:<syntaxhighlight lang="kotlin"> | ||
var setterVisibility: String = "abc" | var setterVisibility: String = "abc" | ||
private set // محدد الوصول هو من النوع الخاص وله تعريف الاستخدام الافتراضيّ | private set // محدد الوصول هو من النوع الخاص وله تعريف الاستخدام الافتراضيّ | ||
var setterWithAnnotation: Any? = null | var setterWithAnnotation: Any? = null | ||
@Inject set // إضافة | @Inject set // إضافة التوصيف إليه باسم Inject | ||
</syntaxhighlight> | </syntaxhighlight> | ||
سطر 86: | سطر 86: | ||
* مُهيَّئةٌ بالقيمة من النوع <code>String</code> أو أيّ نوع أساسي (primitive) آخر | * مُهيَّئةٌ بالقيمة من النوع <code>String</code> أو أيّ نوع أساسي (primitive) آخر | ||
* لا يوجد لها getter مُخصَّص | * لا يوجد لها getter مُخصَّص | ||
وتستخدم مثل هذه الخاصّيّات في | وتستخدم مثل هذه الخاصّيّات في التوصيفات (annotations) مثل:<syntaxhighlight lang="kotlin"> | ||
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" | const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" | ||
مراجعة 10:21، 29 مارس 2018
التصريح عن الخاصّيّات (Declaring Properties)
قد تحتوي الأصناف في لغة Kotlin على الخاصّيّات المعرَّفة إما كقيمٍ متغيّرةٍ عبر الكلمة المفتاحيّة var
أو كقيمٍ ثابتةٍ للقراءة فقط (read-only) عبر الكلمة المفتاحيّة val
، مثل:
class Address {
var name: String = ...
var street: String = ...
var city: String = ...
var state: String? = ...
var zip: String = ...
}
إذ يُمكن الوصول للخصائص عبر اسمها (كما لو كانت حقلًا [field] في لغة Java)، مثل:
fun copyAddress(address: Address): Address {
val result = Address() // لا وجود هنا للكلمة المفتاحيّة new
result.name = address.name // تُستدعى دوال الوصول (accessors)
result.street = address.street
// ...
return result
}
الوصول للخاصّيات باستخدام Getter و Setter
إنّ الصيغة الكاملة للتصريح عن الخاصيّات هي:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
إذ يُعدُّ وجودُ كلّ من التهيئة وعمليات الوصول (getter و setter) أمرًا اختياريًا، ويصبح وجود النوع اختياريًا أيضًا إذا أمكن تحديده من خلال التهيئة (أو من النوع المُعاد في دالة getter) كما في الأمثلة الآتية:
var allByDefault: Int? // تحدث مشكلة: يجب وجود التهيئة بشكل واضح، ودوال الوصول
// الافتراضيّة ضمنيّة
var initialized = 1 // النوع هو نوع الأعداد الصحيحة ودوال الوصول افتراضيّة
أمّا الصيغة الكاملة للتصريح عن خاصّيّات القراءة فقط فهي مختلفةٌ عن سابقتها من ناحيتين: الأولى أنّها تبدأ بالكلمة المفتاحيّة val
بدلًا من var
، والثانية أنه لا وجود لدالة الوصول setter، وهي بالصيغة:
val simple: Int? // للخاصية نوع الأعداد الصحيحة ويجب القيام بعملية التهيئة في الباني ودالة الوصول الافتراضية getter
val inferredType = 1 // لها نوع الأعداد الصحيحة ودالة وصول افتراضية getter
ويُمكن تخصيص دوال الوصول (accessors) مثل عملية تعديل أيّ دالة أخرى وذلك في تعريف الخاصّيّة، مثل تخصيص getter في الشيفرة الآتية:
val isEmpty: Boolean
get() = this.size == 0
أمّا تخصيص الدالة setter فهو كما في الشيفرة:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // تحويل السلسلة النصية وإسناد القيمة إلى خاصّيّة أخرى
}
إذ تعبِّر value
-اصطلاحًا- عن اسم المتحول في setter ويمكن تعديله.
وبدءًا من الإصدار 1.1 يمكن حذف نوع الخاصّيّة إذا أمكن تحديده من خلال getter مثل:
val isEmpty get() = this.size == 0 // لها النوع Boolean
ويمكن في بعض الأحيان تعريفُ (define) أيّ من دوال الوصول (accessors) بدون تعريف بُنيتها (body) وذلك عند الحاجة لتغيير مرئيّة الوصول لها (visibility) أو إضافة التوصيف (annotation) دون تغيير تعريف الاستخدام (implementation) الافتراضيّ، كما هو الحال في الشيفرة الآتية:
var setterVisibility: String = "abc"
private set // محدد الوصول هو من النوع الخاص وله تعريف الاستخدام الافتراضيّ
var setterWithAnnotation: Any? = null
@Inject set // إضافة التوصيف إليه باسم Inject
الحقول المُساعدة (Backing Fields)
لا تُعرَّف الحقول مباشرةً في الأصناف (classes) في لغة Kotlin لذلك فإنه عند الحاجة لحقلٍ مساعدٍ فإن Kotlin تزوِّد به تلقائيًا، ويُشار إليه في دوال الوصول عبر المُحدِّد field
، مثل:
var counter = 0 // تُسند القيمة أيضًا للحقل المساعد مباشرة
set(value) {
if (value >= 0) field = value
}
إذ إن استخدام المُحدِّد field
متاحٌ فقط في دوال الوصول (accessors) لتلك الخاصية.
ويُولَّد الحقل المُساعد للخاصية إذا ما استخدَمت تعريف الاستخدام (implementation) الافتراضيّ لدوال الوصول ولو لمرةٍ واحدةٍ أو في حالة الإشارة لها عبر المُحدِّد field
عند تخصيص أيّ من دوال الوصول، فلن يكون هناك مثلًا حقلٌ مساعدٌ في الشيفرة الآتية:
val isEmpty: Boolean
get() = this.size == 0
الخاصّيّات المُساعدِة (Backing Properties)
عند القيام بما يتعارض مع آليّة الحقول المُساعدة الضمنيّة (المشروحة في الفقرة السابقة) فيُعمَد لاستخدام الخاصيّات المُساعدِة كما هو الحال في الشيفرة الآتية:
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // تُحدد متحولات النوع تلقائيًا
}
return _table ?: throw AssertionError("Set to null by another thread")
}
وهذا يماثل -من كافّة النواحي- ما هو مُعتمدٌ في لغة Java، لأن الوصول إلى الخاصيات من النوع الخاصّ (private) باستخدام الدوال الافتراضيّة للوصول (accessor) مُصمَّمٌ بحيث لا يمكن تجاوزه من قِبل أيّ استدعاءٍ للدوال.
ثوابت الترجمة (Compile-Time Constants)
تٌحدَّد الخاصّيّات التي تُعرَف قيمتها (known) أثناء عملية الترجمة باعتبارها "ثوابت الترجمة" وذلك باستخدام المُحدِّد const
، ولها المتطلَّبات الآتية:
- أن تكون أحدَ العناصر (members) في
object
أو واقعة بمستوى أعلى (top-level) - مُهيَّئةٌ بالقيمة من النوع
String
أو أيّ نوع أساسي (primitive) آخر - لا يوجد لها getter مُخصَّص
وتستخدم مثل هذه الخاصّيّات في التوصيفات (annotations) مثل:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
التهيئة اللاحقة (Late-Initializing) للخاصيات (Properties) والمتحوِّلات (Variables)
تُهيَّأ الخاصّيّات المُعرَّفة بالحالة الطبيعيّة من النوع بدون قيم فارغة (non-null) في الباني (constructor) ولكنّ ذلك لا يكون مناسبًا دائمًا، إذ يُمكن مثلًا أن تُهيَّأ خلال إضافة الاستقلاليّة (dependency injection) أو بتابع الإعداد (setup method) لاختبار البُنية (unit test)، ولا يُمكن حينئذٍ الاعتماد على تهيئةٍ non-null في الباني على الرغم من الرغبة بتجنُّب عمليات التحقُّق من القيم الفارغة عند الإشارة (reference) إلى الخاصّيّات داخل بنية الصنف (class body).
ولمعالجة هذه الحالة تُعتمَد التهيئة اللاحقة عبر المُحدِّد lateinit
مثل:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // ٌإسنادٌ مؤشريّ مباشر
}
}
ويُستخدَم هذا المُحدِّد مع الخاصّيّات من النوع var
المُعرَّفة داخل بُنية الصنف (وليس في الباني الأساسي [primary constructor] وفقط عندما لا يكون هناك تخصيص في دوال الوصول [accessors])، واعتبارًا من الإصدار Kotlin 1.2 يُستخدَم أيضًا للخاصيّات بمستوى أعلى (top-level) وللمتحوِّلات المحلية (local variables)، إذ يجب أن يكون النوع فيها نوعًا أساسيًا (primitive) ولا يقبل القيم الفارغة (non-null).
و ينتُج عن محاولة الوصول إلى الخاصّيّة قبل تهيئتها اللاحقة استثناءٌ (exception) خاصّ يُفيد بأنّه قد تمّ الوصول إلى الخاصّيّة قبل تهيئتها.
التأكد من التهيئة اللاحقة للمتحول var
(بدءًا من الإصدار 1.2)
تُستخدَم .isInitialized
مع مرجعيّة (reference) الخاصّيّة المُحدَّدة بالمُعرِّفين lateinit var
للتحقُّق من إتمام عملية التهيئة اللاحقة لها، مثل:
if (foo::bar.isInitialized) {
println(foo.bar)
}
ويُسمَح بهذا التحقق فقط عندما يكون الوصول لهذه الخاصيّة مُتاحًا (accessible)، أي أنها مُعرَّفةٌ من نفس النوع أو أحد الأنواع الخارجية (outer) أو بمستوى أعلى (top-level) في نفس الملف (file).
إعادة تعريف الخاصّيّات (Overriding Properties)
راجع إعادة تعريف الخاصّيّات (Overriding Properties)
الخاصّيّات المُعمَّمة (Delegated Properties)
إنّ النوع الأكثر شيوعًا للخاصّيّات مُعدٌّ للقراءة من (وربما الكتابة في) الحقل المُساعِد، أمّا في حالة تخصيص دوال الوصول (getter أو setter) فيُمكن تعريف استخدام (implement) أيّ سلوكٍ للخاصّيّة، وهناك أنماطٌ مُحدَّدةٌ لكيفية عمل الخاصّيّة، مثل: القيم الكسولة (lazy value) أو القراءة من map من خلال المفتاح (key)، أو الوصول إلى قاعدة البيانات أو الإعلام عند محاولة الوصول أو ...إلخ، ومثلُ هذه الحالات يُمكن تعريف استخدامها كمكتباتٍ (libraries) باستخدام الخاصّيّات المُعمَّمة (delegated properties).