الفرق بين المراجعتين لصفحة: «Kotlin/annotations»

من موسوعة حسوب
طلا ملخص تعديل
تعديل مصطلح متحول
سطر 35: سطر 35:


=== البواني (Constructors) ===
=== البواني (Constructors) ===
وقد يكون للتوصيفات بوانٍ تقبل المتحوّلات (parameters)، مثل:<syntaxhighlight lang="kotlin">
وقد يكون للتوصيفات بوانٍ تقبل المعاملات (parameters)، مثل:<syntaxhighlight lang="kotlin">
annotation class Special(val why: String)
annotation class Special(val why: String)


@Special("example") class Foo {}
@Special("example") class Foo {}
</syntaxhighlight>حيث يُسمَح بأنواع المتحولات الآتية:
</syntaxhighlight>حيث يُسمَح بأنواع المعاملات الآتية:
* الأنواع الموافقة للأنواع الأساسيّة (primitive) في لغة Java  (مثل int و Long و... وإلخ.)
* الأنواع الموافقة للأنواع الأساسيّة (primitive) في لغة Java  (مثل int و Long و... وإلخ.)
* السلاسل النصيّة (strings)
* السلاسل النصيّة (strings)
سطر 48: سطر 48:
ولا يُسمَح أن تكون هذه الأنواع nullable لأن JVM لا تدعم تخزين القيمة <code>null</code> في خاصّة التوصيف (annotation attribute).
ولا يُسمَح أن تكون هذه الأنواع nullable لأن JVM لا تدعم تخزين القيمة <code>null</code> في خاصّة التوصيف (annotation attribute).


وعند استخدام التوصيف كمتحولٍ وسيطٍ (parameter) لتوصيفٍ آخر فلا يُسبَق اسمه بالرمز <code>@</code> مثل:<syntaxhighlight lang="kotlin">
وعند استخدام التوصيف كمعاملٍ (parameter) لتوصيفٍ آخر فلا يُسبَق اسمه بالرمز <code>@</code> مثل:<syntaxhighlight lang="kotlin">
annotation class ReplaceWith(val expression: String)
annotation class ReplaceWith(val expression: String)


سطر 56: سطر 56:


@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))
@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))
</syntaxhighlight>وعند تحديد الصنف (class) كمتحوّلٍ للتوصيف فيُستخدَم صنف Kotlin والمُسمّى <code>KClass</code> ، والذي يُحوِّله المُترجِم إلى صنف Java تلقائيًا بحيث تصبح شيفرة Java قادرةً على رؤية التوصيفات والمتحوّلات بشكلٍ طبيعيّ، مثل:<syntaxhighlight lang="kotlin">
</syntaxhighlight>وعند تحديد الصنف (class) كوسيطٍ للتوصيف فيُستخدَم صنف Kotlin والمُسمّى <code>KClass</code> ، والذي يُحوِّله المُترجِم إلى صنف Java تلقائيًا بحيث تصبح شيفرة Java قادرةً على رؤية التوصيفات والوسائط بشكلٍ طبيعيّ، مثل:<syntaxhighlight lang="kotlin">
import kotlin.reflect.KClass
import kotlin.reflect.KClass


سطر 72: سطر 72:


== أهداف مواقع استخدام التوصيفات (Annotation Use-site Targets) ==
== أهداف مواقع استخدام التوصيفات (Annotation Use-site Targets) ==
عند توصيف إحدى الخاصّيّات (property) أو متحولات الباني الأساسي (primary constructor parameter) فهناك الكثير من عناصر Java المُولَّدة من عناصر Kotlin الموافقة لها، وبالتالي تتعدّد المواقع المُمكِنة لاستخدام التوصيف في شيفرة Java bytecode المُولَّدة، ولتحديد كيفيّة توليد التوصيف بشكلٍ دقيقٍ، تُستخدَم الصيغة الآتية:<syntaxhighlight lang="kotlin">
عند توصيف إحدى الخاصّيّات (property) أو معاملات الباني الأساسي (primary constructor parameter) فهناك الكثير من عناصر Java المُولَّدة من عناصر Kotlin الموافقة لها، وبالتالي تتعدّد المواقع المُمكِنة لاستخدام التوصيف في شيفرة Java bytecode المُولَّدة، ولتحديد كيفيّة توليد التوصيف بشكلٍ دقيقٍ، تُستخدَم الصيغة الآتية:<syntaxhighlight lang="kotlin">
class Example(@field:Ann val foo,    // توصيف حقل Java
class Example(@field:Ann val foo,    // توصيف حقل Java
               @get:Ann val bar,      // توصيف الوصول عبر getter
               @get:Ann val bar,      // توصيف الوصول عبر getter
سطر 91: سطر 91:
* <code>get</code> (الحصول على الخاصّيّة عبر getter)
* <code>get</code> (الحصول على الخاصّيّة عبر getter)
* <code>set</code> (ضبط الخاصّيّة عبر setter)
* <code>set</code> (ضبط الخاصّيّة عبر setter)
* <code>receiver</code> (متحول المستقبِل للدالة الإضافيّة [extension function] أو الخاصّيّة)
* <code>receiver</code> (معامل المستقبِل للدالة الإضافيّة [extension function] أو الخاصّيّة)
* <code>param</code> (متحول الباني [constructor])
* <code>param</code> (معامل الباني [constructor])
* <code>setparam</code> (متحول ضبط الخاصّيّة عبر setter)
* <code>setparam</code> (معامل ضبط الخاصّيّة عبر setter)
* <code>delegate</code> (حقل تخزين نسخة التعميم (delegate instance) من الخاصّيّات المُعمَّمة)
* <code>delegate</code> (حقل تخزين نسخة التعميم (delegate instance) من الخاصّيّات المُعمَّمة)
ولتوصيف متحول المستقبِل (receiver parameter) في الدالة الإضافيّة (extension function) تُستخدَم الصيغة الآتية:<syntaxhighlight lang="kotlin">
ولتوصيف معامل المستقبِل (receiver parameter) في الدالة الإضافيّة (extension function) تُستخدَم الصيغة الآتية:<syntaxhighlight lang="kotlin">
fun @receiver:Fancy String.myExtension() { }
fun @receiver:Fancy String.myExtension() { }
</syntaxhighlight>وإن لم يُحدَّد هدف موقع الاستخدام (use-site target) فسيصبح الهدف بحسب توصيف ‎<code>@Target</code> من التوصيف المُستخدَم، أمّا إن كان هناك أكثرُ من هدفٍ محتملٍ، فسيتم اعتماد أولّ عنصرٍ ممكنٍ من هذه القائمة:
</syntaxhighlight>وإن لم يُحدَّد هدف موقع الاستخدام (use-site target) فسيصبح الهدف بحسب توصيف ‎<code>@Target</code> من التوصيف المُستخدَم، أمّا إن كان هناك أكثرُ من هدفٍ محتملٍ، فسيتم اعتماد أولّ عنصرٍ ممكنٍ من هذه القائمة:
سطر 119: سطر 119:
     }
     }
}
}
</syntaxhighlight>إذ لا يمكن استخدام الصيغة الاعتياديّة لاستدعاء الدالة لتمرير المتحولات (arguments passing) لأن ترتيب المتحولات في التوصيف المكتوب بلغة Java غير معرَّف، بل تُستخدَم بدلًا عن ذلك صيغة المتحولات المُسمّاة كما في الشيفرة:<syntaxhighlight lang="java">
</syntaxhighlight>إذ لا يمكن استخدام الصيغة الاعتياديّة لاستدعاء الدالة لتمرير الوسائط (arguments passing) لأن ترتيب الوسائط في التوصيف المكتوب بلغة Java غير معرَّف، بل تُستخدَم بدلًا عن ذلك صيغة الوسائط المُسمّاة كما في الشيفرة:<syntaxhighlight lang="java">
// Java شيفرة
// Java شيفرة
public @interface Ann {
public @interface Ann {
سطر 128: سطر 128:
// Kotlin شيفرة
// Kotlin شيفرة
@Ann(intValue = 1, stringValue = "abc") class C
@Ann(intValue = 1, stringValue = "abc") class C
</syntaxhighlight>وكما هو الحال في لغة Java فإن المتحول <code>value</code> هو حالةٌ خاصةٌ لأن قيمته تُحدَّد بدون استخدام اسمٍ صريحٍ (explicit)، كما في الشيفرة:<syntaxhighlight lang="java">
</syntaxhighlight>وكما هو الحال في لغة Java فإن المعامل  <code>value</code> هو حالةٌ خاصةٌ لأن قيمته تُحدَّد بدون استخدام اسمٍ صريحٍ (explicit)، كما في الشيفرة:<syntaxhighlight lang="java">
// Java شيفرة
// Java شيفرة
public @interface AnnWithValue {
public @interface AnnWithValue {
سطر 138: سطر 138:
</syntaxhighlight>
</syntaxhighlight>


=== استخدام المصفوفات كمتحولاتٍ للتوصيف ===
=== استخدام المصفوفات كمعاملات للتوصيف ===
إن كان المتحول <code>value</code> في لغة Java  مصفوفةً فسيصبح متحولًا من النوع <code>vararg</code> في لغة Kotlin، مثل:<syntaxhighlight lang="java">
إن كان الوسيط<code>value</code> في لغة Java  مصفوفةً فسيصبح معاملًا من النوع <code>vararg</code> في لغة Kotlin، مثل:<syntaxhighlight lang="java">
// Java شيفرة
// Java شيفرة
public @interface AnnWithArrayValue {
public @interface AnnWithArrayValue {
سطر 147: سطر 147:
// Kotlin شيفرة
// Kotlin شيفرة
@AnnWithArrayValue("abc", "foo", "bar") class C
@AnnWithArrayValue("abc", "foo", "bar") class C
</syntaxhighlight>أمّا إن كانت المتحولات الأخرى على شكل مصفوفاتٍ فيجب استخدام صيغة القيم المباشرة (literals) للمصفوفة (بدءًا من الإصدار Kotlin 1.2 ) أو استخدام <code>arrayOf(...)</code>‎، مثل:<syntaxhighlight lang="java">
</syntaxhighlight>أمّا إن كانت الوسائط الأخرى على شكل مصفوفاتٍ فيجب استخدام صيغة القيم المباشرة (literals) للمصفوفة (بدءًا من الإصدار Kotlin 1.2 ) أو استخدام <code>arrayOf(...)</code>‎، مثل:<syntaxhighlight lang="java">
// Java شيفرة
// Java شيفرة
public @interface AnnWithArrayMethod {
public @interface AnnWithArrayMethod {

مراجعة 17:47، 4 يوليو 2018

التصريح عن التوصيف (Annotation Declaration)

تُعدُّ التوصيفات إحدى الوسائل لإضافة بياناتٍ توصيفيّةٍ (metadata) إلى الشيفرة، وللتصريح عن التوصيف يُضاف المُحدِّد annotation قبل اسم الصنف، مثل:

annotation class Fancy

وقد تُحدَّد بعض خواصّ التوصيفات (annotation attributes) باستخدام التوصيفات الآتية (meta-annotations) لتوصيفات الصنف:

  • ‎@Target لتحديد نوع العناصر التي يمكن توصيفها مثل الأصناف (classes) والدوال (functions) والخاصّيّات (properties) والتعابير (expressions) و... إلخ.
  • @Retention لتحديد فيما إن كان التوصيف مُخزَّنًا في ملفات الأصناف المُترجَمة، أو مرئيًا عبر انعكاسٍ (reflection) أثناء التنفيذ (runtime) (وكلاهما محقُّق بالحالة الافتراضية)
  • @Repeatable للسماح باستخدام نفس التوصيف على كائن واحدٍ لعدّة مرات
  • @MustBeDocumented  لتحديد التوصيف كجزءٍ من الواجهة العامّة (public API) يجب تضمينه في الصنف (class) أو ترويسة التابع (method signature) الموجودَين في التوثيق المُولَّد للواجهة API

مثال:

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
        AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy

الاستخدام (Usage)

ليكن الصنف Foo الآتي:

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}

لتوصيف الباني الأساسيّ (primary constructor) للصنف تجب إضافة الكلمة المفتاحيّة constructor إلى تصريحه مسبوقةً بالتوصيف كما في الشيفرة:

class Foo @Inject constructor(dependency: MyDependency) {
    // ...
}

كما ويمكن توصيف الوصول (accessing) إلى الخاصّيّات (get أو set) كما في الشيفرة:

class Foo {
    var x: MyDependency? = null
        @Inject set
}

البواني (Constructors)

وقد يكون للتوصيفات بوانٍ تقبل المعاملات (parameters)، مثل:

annotation class Special(val why: String)

@Special("example") class Foo {}

حيث يُسمَح بأنواع المعاملات الآتية:

  • الأنواع الموافقة للأنواع الأساسيّة (primitive) في لغة Java (مثل int و Long و... وإلخ.)
  • السلاسل النصيّة (strings)
  • الأصناف (classes) (بالصيغة Foo::class)
  • الثوابت المتعدِّدة (enums)
  • التوصيفات الأخرى
  • مصفوفة (array) من أيّ من الأنواع السابقة

ولا يُسمَح أن تكون هذه الأنواع nullable لأن JVM لا تدعم تخزين القيمة null في خاصّة التوصيف (annotation attribute).

وعند استخدام التوصيف كمعاملٍ (parameter) لتوصيفٍ آخر فلا يُسبَق اسمه بالرمز @ مثل:

annotation class ReplaceWith(val expression: String)

annotation class Deprecated(
        val message: String,
        val replaceWith: ReplaceWith = ReplaceWith(""))

@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))

وعند تحديد الصنف (class) كوسيطٍ للتوصيف فيُستخدَم صنف Kotlin والمُسمّى KClass ، والذي يُحوِّله المُترجِم إلى صنف Java تلقائيًا بحيث تصبح شيفرة Java قادرةً على رؤية التوصيفات والوسائط بشكلٍ طبيعيّ، مثل:

import kotlin.reflect.KClass

annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any>)

@Ann(String::class, Int::class) class MyClass

تعابير Lambdas

تُضاف التوصيفات لتعابير lambdas عبر تطبيقها على التابع inoke()‎ الذي من خلاله ستُولَّد بُنية lambda، وهذا يُفيد في بيئات العمل (frameworks ) مثل Quasar التي تستخدِم التوصيفات للتحكم بالتزامن (concurrency)، مثل:

annotation class Suspendable

val f = @Suspendable { Fiber.sleep(10) }

أهداف مواقع استخدام التوصيفات (Annotation Use-site Targets)

عند توصيف إحدى الخاصّيّات (property) أو معاملات الباني الأساسي (primary constructor parameter) فهناك الكثير من عناصر Java المُولَّدة من عناصر Kotlin الموافقة لها، وبالتالي تتعدّد المواقع المُمكِنة لاستخدام التوصيف في شيفرة Java bytecode المُولَّدة، ولتحديد كيفيّة توليد التوصيف بشكلٍ دقيقٍ، تُستخدَم الصيغة الآتية:

class Example(@field:Ann val foo,    // توصيف حقل Java
              @get:Ann val bar,      // توصيف الوصول عبر getter
              @param:Ann val quux)   // توصيف باني أساسي

وبالإمكان استخدام نفس الصيغة السابقة لتوصيف كامل الملفّ، إذ يُوضَع التوصيف بالهدف file في المستوى الأعلى (top-level) من الملف قبل مُوجِّه الحزمة (package directive) أو قبل أيٍّ من عمليات الاستيراد (import) إن كان الملف واقعًا في الحزمة الافتراضية، مثل:

@file:JvmName("Foo")

package org.jetbrains.demo

وعند وجود أكثر من توصيفٍ بنفس الهدف تُضاف الأقواس [] بعد الهدف مباشرةً ويُوضع كامل التوصيف داخلها وذلك لمنع التكرار ، مثل:

class Example {
     @set:[Inject VisibleForTesting]
     var collaborator: Collaborator
}

تدعم لغة Kotlin القائمة الآتية من الأهداف:

  • file
  • property (توصيف هذا الهدف غير مرئيٍّ بالنسبة للغة Java)
  • field
  • get (الحصول على الخاصّيّة عبر getter)
  • set (ضبط الخاصّيّة عبر setter)
  • receiver (معامل المستقبِل للدالة الإضافيّة [extension function] أو الخاصّيّة)
  • param (معامل الباني [constructor])
  • setparam (معامل ضبط الخاصّيّة عبر setter)
  • delegate (حقل تخزين نسخة التعميم (delegate instance) من الخاصّيّات المُعمَّمة)

ولتوصيف معامل المستقبِل (receiver parameter) في الدالة الإضافيّة (extension function) تُستخدَم الصيغة الآتية:

fun @receiver:Fancy String.myExtension() { }

وإن لم يُحدَّد هدف موقع الاستخدام (use-site target) فسيصبح الهدف بحسب توصيف ‎@Target من التوصيف المُستخدَم، أمّا إن كان هناك أكثرُ من هدفٍ محتملٍ، فسيتم اعتماد أولّ عنصرٍ ممكنٍ من هذه القائمة:

  • aram
  • property
  • field

التوصيفات في لغة Java

تتوافق التوصيفات في لغة Java بشكل تامّ مع لغة Kotlin، مثل:

import org.junit.Test
import org.junit.Assert.*
import org.junit.Rule
import org.junit.rules.*

class Tests {
    // تطبيق التوصيف @Rule
    // على الوصول للخاصية عبر getter
    @get:Rule val tempFolder = TemporaryFolder()

    @Test fun simple() {
        val f = tempFolder.newFile()
        assertEquals(42, getTheAnswer())
    }
}

إذ لا يمكن استخدام الصيغة الاعتياديّة لاستدعاء الدالة لتمرير الوسائط (arguments passing) لأن ترتيب الوسائط في التوصيف المكتوب بلغة Java غير معرَّف، بل تُستخدَم بدلًا عن ذلك صيغة الوسائط المُسمّاة كما في الشيفرة:

// Java شيفرة
public @interface Ann {
    int intValue();
    String stringValue();
}

وفي لغة Kotlin بالشكل:

// Kotlin شيفرة
@Ann(intValue = 1, stringValue = "abc") class C

وكما هو الحال في لغة Java فإن المعامل value هو حالةٌ خاصةٌ لأن قيمته تُحدَّد بدون استخدام اسمٍ صريحٍ (explicit)، كما في الشيفرة:

// Java شيفرة
public @interface AnnWithValue {
    String value();
}

وفي لغة Kotlin:

// Kotlin شيفرة
@AnnWithValue("abc") class C

استخدام المصفوفات كمعاملات للتوصيف

إن كان الوسيطvalue في لغة Java مصفوفةً فسيصبح معاملًا من النوع vararg في لغة Kotlin، مثل:

// Java شيفرة
public @interface AnnWithArrayValue {
    String[] value();
}

وفي لغة Kotlin تصبح الشيفرة:

// Kotlin شيفرة
@AnnWithArrayValue("abc", "foo", "bar") class C

أمّا إن كانت الوسائط الأخرى على شكل مصفوفاتٍ فيجب استخدام صيغة القيم المباشرة (literals) للمصفوفة (بدءًا من الإصدار Kotlin 1.2 ) أو استخدام arrayOf(...)‎، مثل:

// Java شيفرة
public @interface AnnWithArrayMethod {
    String[] names();
}

وتصبح في لغة Kotlin:

// Kotlin 1.2 بدءًا من الإصدار
@AnnWithArrayMethod(names = ["abc", "foo", "bar"]) 
class C

// الإصدارات الأقدم
@AnnWithArrayMethod(names = arrayOf("abc", "foo", "bar")) 
class D

الوصول إلى الخاصّيّات (Properties) عبر نسخة التوصيف (Annotation Instance)

تظهر قيم نسخة التوصيف (annotation instance) وكأنها خاصّيّات في شيفرة Kotlin كما يلي:

// Java شيفرة
public @interface Ann {
    int value();
}

وتصبح في لغة Kotlin:

// Kotlin شيفرة
fun foo(ann: Ann) {
    val i = ann.value
}

مصادر