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

من موسوعة حسوب
ط إزالة التشكيل من العنوان
ط تعديل مصطلح متحول
 
(2 مراجعات متوسطة بواسطة نفس المستخدم غير معروضة)
سطر 14: سطر 14:
</syntaxhighlight>
</syntaxhighlight>


== التحويلات الذكيّة (Smart Casts) ==
== التحويلات الذكية (Smart Casts) ==
لا حاجة في كثيرٍ من الأحيان لجعل التحويل صريحًا (explicit) في لغة Kotlin لأنّ المترجم (compiler) يتتبَّع عمليات التحقُّق (عبر المعامل <code>is</code>) والتحويل الصريح للقيم الثابتة (immutable) ويُحوِّلها تلقائيًا وبشكلٍ آمنٍ عندما تدعو الحاجة لذلك، كما في الشيفرة:<syntaxhighlight lang="kotlin">
لا حاجة في كثيرٍ من الأحيان لجعل التحويل صريحًا (explicit) في لغة Kotlin لأنّ المترجم (compiler) يتتبَّع عمليات التحقُّق (عبر المعامل <code>is</code>) والتحويل الصريح للقيم الثابتة (immutable) ويُحوِّلها تلقائيًا وبشكلٍ آمنٍ عندما تدعو الحاجة لذلك، كما في الشيفرة:<syntaxhighlight lang="kotlin">
fun demo(x: Any) {
fun demo(x: Any) {
     if (x is String) {
     if (x is String) {
         print(x.length) // x سيُحوّل المتحول
         print(x.length) // x سيُحوّل المعامل
                         // String تلقائيًا إلى النوع
                         // String تلقائيًا إلى النوع
     }
     }
سطر 24: سطر 24:
</syntaxhighlight>حيث يُعدُّ التحويل "آمنًا" إن كان التحقُّق المنفيّ يؤدي إلى أمر الرجوع <code>return</code> مثل:<syntaxhighlight lang="kotlin">
</syntaxhighlight>حيث يُعدُّ التحويل "آمنًا" إن كان التحقُّق المنفيّ يؤدي إلى أمر الرجوع <code>return</code> مثل:<syntaxhighlight lang="kotlin">
     if (x !is String) return
     if (x !is String) return
     print(x.length) // x سيُحوّل المتحول
     print(x.length) // x سيُحوّل المعامل
                     // String تلقائيًا إلى النوع
                     // String تلقائيًا إلى النوع
</syntaxhighlight>أو في الجانب الأيمن من المعامِلين <code>&&</code> أو <code>||</code> كما في الشيفرة:<syntaxhighlight lang="kotlin">
</syntaxhighlight>أو في الجانب الأيمن من المعامِلين <code>&&</code> أو <code>||</code> كما في الشيفرة:<syntaxhighlight lang="kotlin">
     // سيُحوّل المتحول تلقائيًا إلى نوع السلسلة النصيّة  
     // سيُحوّل المتغير تلقائيًا إلى نوع السلسلة النصيّة  
     // لوقوعه على الجانب الأيمن من المعامل || في الشرط  
     // لوقوعه على الجانب الأيمن من المعامل || في الشرط  
     if (x !is String || x.length == 0) return
     if (x !is String || x.length == 0) return


     // سيُحوّل المتحول تلقائيًا إلى نوع السلسلة النصيّة  
     // سيُحوّل المتغير تلقائيًا إلى نوع السلسلة النصيّة  
     // لوقوعه على الجانب الأيمن من المعامل && في الشرط  
     // لوقوعه على الجانب الأيمن من المعامل && في الشرط  
     if (x is String && x.length > 0) {
     if (x is String && x.length > 0) {
سطر 42: سطر 42:
     is IntArray -> print(x.sum())
     is IntArray -> print(x.sum())
}
}
</syntaxhighlight>ولا تُجرَى التحويلات الذكيّة عندما يكون المترجِم (compiler) غير قادرٍ على ضمان عدم تغيُّر المتحوِّل بين عملية التحقُّق والاستخدام، وبتفصيلٍ أكثر؛ تُجرَى التحويلات الذكيّة في الحالات الآتية:
</syntaxhighlight>ولا تُجرَى التحويلات الذكيّة عندما يكون المترجِم (compiler) غير قادرٍ على ضمان عدم تغيُّر المتغيِّر بين عملية التحقُّق والاستخدام، وبتفصيلٍ أكثر؛ تُجرَى التحويلات الذكيّة في الحالات الآتية:
* '''المتحوّلات المحليّة (local) من النوع <code>val</code>:''' دائمًا باستثناء [[Kotlin/delegated properties|الخاصّيّات المُعمَّمة المحليّة (local delegated properties)]].
* '''المتغيِّرات المحليّة (local) من النوع <code>val</code>:''' دائمًا باستثناء [[Kotlin/delegated properties|الخاصّيّات المُعمَّمة المحليّة (local delegated properties)]].
* '''الخاصّيّات (properties) من النوع <code>val</code>:''' فقط إن كانت الخاصّيّات خاصةً (private) أو داخليةً (internal) أو أُجري التحقُّق منها في نفس الوحدة (module) التي تحتوي على تصريح الخاصّيّة، ولا تتمّ التحويلات في حالة الخاصّيّات المفتوحة (open) أو تلك التي يكون فيها getter مُخصَّص.
* '''الخاصّيّات (properties) من النوع <code>val</code>:''' فقط إن كانت الخاصّيّات خاصةً (private) أو داخليةً (internal) أو أُجري التحقُّق منها في نفس الوحدة (module) التي تحتوي على تصريح الخاصّيّة، ولا تتمّ التحويلات في حالة الخاصّيّات المفتوحة (open) أو تلك التي يكون فيها getter مُخصَّص.
* '''المتحولات المحليّة من النوع <code>var</code>:''' فقط إن لم يتغيّر المتحوّل ما بين التحقُّق والاستخدام ولم يخضغ لتعبير lambda يحوِّل من قيمته أو لم يكن خاصّيّةً مُعمَّمةً محليّةً.
* '''المتغيِّرات المحليّة من النوع <code>var</code>:''' فقط إن لم يتغيّر المتغيِّر ما بين التحقُّق والاستخدام ولم يخضغ لتعبير lambda يحوِّل من قيمته أو لم يكن خاصّيّةً مُعمَّمةً محليّةً.
* '''الخاصّيّات من النوع <code>var</code>:''' أبدًا، وذلك لأنّ المتحول قد يتغيّر في أيِّ لحظةٍ ممكنةٍ وعبر أيّ شيفرة أخرى.
* '''الخاصّيّات من النوع <code>var</code>:''' أبدًا، وذلك لأنّ المتغيِّر قد يتغيّر في أيِّ لحظةٍ ممكنةٍ وعبر أيّ شيفرة أخرى.


== معامل التحويل غير الآمن ==
== معامل التحويل غير الآمن ==
سينتُج عن استخدام معامل التحويل استثناءٌ (exception) إن لم يكن التحويل متاحًا، وهذا ما ندعوه بالتحويل غير الآمن (unsafe) ويُعتمَد في Kotlin على المعامل <code>as</code> بالصيغة الداخليّة (infix) كما في الشيفرة:<syntaxhighlight lang="kotlin">
سينتُج عن استخدام معامل التحويل استثناءٌ (exception) إن لم يكن التحويل متاحًا، وهذا ما ندعوه بالتحويل غير الآمن (unsafe) ويُعتمَد في Kotlin على المعامل <code>as</code> بالصيغة الداخليّة (infix) كما في الشيفرة:<syntaxhighlight lang="kotlin">
val x: String = y as String
val x: String = y as String
</syntaxhighlight>إذ لا يُمكن تحويل القيمة الفارغة <code>null</code> إلى النوع <code>String</code> لأنّه ليس نوعًا nullable، وبالتالي فإن كانت قيمة المتحول <code>y</code> هي <code>null</code> سينتُج استثناء. ولتحقيق التماثل مع صيغة لغة Java يجب أن يكون النوع nullable في الجانب الأيمن من التحويل كما في الشيفرة:<syntaxhighlight lang="kotlin">
</syntaxhighlight>إذ لا يُمكن تحويل القيمة الفارغة <code>null</code> إلى النوع <code>String</code> لأنّه ليس نوعًا nullable، وبالتالي فإن كانت قيمة المتغيِّر <code>y</code> هي <code>null</code> سينتُج استثناء. ولتحقيق التماثل مع صيغة لغة Java يجب أن يكون النوع nullable في الجانب الأيمن من التحويل كما في الشيفرة:<syntaxhighlight lang="kotlin">
val x: String? = y as String?
val x: String? = y as String?
</syntaxhighlight>
</syntaxhighlight>
سطر 61: سطر 61:


== إزالة الأنواع (Type Erasure) والتحقُّق من الأنواع المُعمَّمة (Generic Type) ==
== إزالة الأنواع (Type Erasure) والتحقُّق من الأنواع المُعمَّمة (Generic Type) ==
تضمن لغة Kotlin أمان الأنواع (type-safety) -بما فيها [[Kotlin/generics|الأنواع المُعمَّمة (generics)]]- أثناء الترجمة (compilation) فقط، أما أثناء التنفيذ (runtime) فقد لا تحتوي بعض الكائنات من الأنواع المُعمَّمة على معلوماتٍ عن متحوّلات أنواعها الفعليّة، فيُزال النوع <code>List<Foo></code>‎ مثلًا ليشمله النوع <code>List<*></code>‎، إذ لا توجد طريقةٌ للتحقُّق من انتماء الكائن إلى نوعٍ مُعمَّمٍ (بمتحولات الأنواع) أثناء التنفيذ.
تضمن لغة Kotlin أمان الأنواع (type-safety) -بما فيها [[Kotlin/generics|الأنواع المُعمَّمة (generics)]]- أثناء الترجمة (compilation) فقط، أما أثناء التنفيذ (runtime) فقد لا تحتوي بعض الكائنات من الأنواع المُعمَّمة على معلوماتٍ عن وسائط أنواعها الفعليّة، فيُزال النوع <code>List<Foo></code>‎ مثلًا ليشمله النوع <code>List<*></code>‎، إذ لا توجد طريقةٌ للتحقُّق من انتماء الكائن إلى نوعٍ مُعمَّمٍ (بوسائط الأنواع) أثناء التنفيذ.


وبالتالي فإن المُترجِم (compiler) يمنع عمليات التحقُّق بالمعامل <code>is</code> أثناء التنفيذ بسبب إزالة الأنواع (type erasure) مثل: <code>ints is List<Int></code>‎ أو <code>list is T</code> ، ولكن من الممكن التحقُّق من الكائنات المخالفة [[Kotlin/generics|للأنواع ذات الإسقاط الواسع (star-projected type)]] كما في الشيفرة:<syntaxhighlight lang="kotlin">
وبالتالي فإن المُترجِم (compiler) يمنع عمليات التحقُّق بالمعامل <code>is</code> أثناء التنفيذ بسبب إزالة الأنواع (type erasure) مثل: <code>ints is List<Int></code>‎ أو <code>list is T</code> ، ولكن من الممكن التحقُّق من الكائنات المخالفة [[Kotlin/generics|للأنواع ذات الإسقاط الواسع (star-projected type)]] كما في الشيفرة:<syntaxhighlight lang="kotlin">
سطر 67: سطر 67:
     something.forEach { println(it) } // `Any?` ستُجعَل العناصر من النوع
     something.forEach { println(it) } // `Any?` ستُجعَل العناصر من النوع
}
}
</syntaxhighlight>وبشكلٍ مماثلٍ؛ وبما أنه تمّ التحقُّق الستاتيكيّ لمتحولات الأنواع (type arguments) للكائن (instance) أثناء الترجمة فيمكن كذلك التحقُّق بالمعامل <code>is</code> أو القيام بأيّ تحويلٍ يحتوي على الجزء غير المُعمَّم (non-generic part) من هذا النوع، حيث ستُحذف الأقواس <code><></code> كما في الشيفرة:<syntaxhighlight lang="kotlin">
</syntaxhighlight>وبشكلٍ مماثلٍ؛ وبما أنه تمّ التحقُّق الستاتيكيّ لوسائط الأنواع (type arguments) للكائن (instance) أثناء الترجمة فيمكن كذلك التحقُّق بالمعامل <code>is</code> أو القيام بأيّ تحويلٍ يحتوي على الجزء غير المُعمَّم (non-generic part) من هذا النوع، حيث ستُحذف الأقواس <code><></code> كما في الشيفرة:<syntaxhighlight lang="kotlin">
fun handleStrings(list: List<String>) {
fun handleStrings(list: List<String>) {
     if (list is ArrayList) {
     if (list is ArrayList) {
سطر 74: سطر 74:
     }
     }
}
}
</syntaxhighlight>ويُسمَح باستخدام نفس صيغة التعامل مع متحوّلات النوع المحذوفة في حالة التحويلات التي لا تأخذ متحولات النوع بالحسبان، مثل: <code>list as ArrayList</code>.
</syntaxhighlight>ويُسمَح باستخدام نفس صيغة التعامل مع وسائط النوع المحذوفة في حالة التحويلات التي لا تأخذ وسائط النوع بالحسبان، مثل: <code>list as ArrayList</code>.


وفي حالة الدوال السطريّة (inline functions) بمتحولات نوعٍ reified (والمُضمَّنة [inlined] في كلّ موقع استدعاءٍ للدالة) فيُتاح التحقُّق من متحولات النوع بالشكل: <code>arg is T</code> ، أما إن كان <code>arg</code> كائنًا (instance) من النوع المُعمَّم ذاته فستُزال كذلك متحولات النوع فيه، مثل:<syntaxhighlight lang="kotlin">
وفي حالة الدوال المباشرة (inline functions) بمعاملات نوعٍ reified (والمُضمَّنة [inlined] في كلّ موقع استدعاءٍ للدالة) فيُتاح التحقُّق من معاملات النوع بالشكل: <code>arg is T</code> ، أما إن كان <code>arg</code> كائنًا (instance) من النوع المُعمَّم ذاته فستُزال كذلك وسائط النوع فيه، مثل:<syntaxhighlight lang="kotlin">
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
     if (first !is A || second !is B) return null
     if (first !is A || second !is B) return null
سطر 91: سطر 91:


== التحويلات غير المتحقَّق منها (Unchecked Casts) ==
== التحويلات غير المتحقَّق منها (Unchecked Casts) ==
إنّ مفهوم إزالة الأنواع (type erasure) في Kotlin يمنع -كما ذكرنا آنفًا- عمليات التحقُّق من متحولات النوع الفعليّ لكائنات الأنواع المُعمَّمة (generic type instances) أثناء التنفيذ (runtime)، وقد لا ترتبط الأنواع المُعمَّمة فيما بينها بما يكفي المترجمَ لضمان أمان الأنواع، وعلى الرغم من ذلك تشتمل (imply) بعض البرامج عالية المستوى (high-level) على أمان الأنواع، مثل:<syntaxhighlight lang="kotlin">
إنّ مفهوم إزالة الأنواع (type erasure) في Kotlin يمنع -كما ذكرنا آنفًا- عمليات التحقُّق من وسائط النوع الفعليّ لكائنات الأنواع المُعمَّمة (generic type instances) أثناء التنفيذ (runtime)، وقد لا ترتبط الأنواع المُعمَّمة فيما بينها بما يكفي المترجمَ لضمان أمان الأنواع، وعلى الرغم من ذلك تشتمل (imply) بعض البرامج عالية المستوى (high-level) على أمان الأنواع، مثل:<syntaxhighlight lang="kotlin">
fun readDictionary(file: File): Map<String, *> = file.inputStream().use {  
fun readDictionary(file: File): Map<String, *> = file.inputStream().use {  
     TODO("Read a mapping of strings to arbitrary elements.")
     TODO("Read a mapping of strings to arbitrary elements.")
سطر 106: سطر 106:
ولتجنب ذلك النوع من التحويلات تُعاد هيكلة البرنامج؛ فبالإمكان إضافة الواجهتين: <code>DictionaryReader<T>‎</code> و <code>DictionaryWriter<T></code>‎ في البرنامج السابق واللتان تحتويان على تعريف استخدامٍ (implementation) آمنٍ لأنواع مختلفةٍ، ومن الممكن أيضًا إضافة تجريدات (abstractions) لنقل التحويل غير المتحقَّق منه (unchecked cast) من الشيفرة المستدعِية (calling cod) إلى تعريف الاستخدام (implementation)، ومن المفيد كذلك استخدام [[Kotlin/generics|التغيُّر المُعمَّم (generic variance)]].
ولتجنب ذلك النوع من التحويلات تُعاد هيكلة البرنامج؛ فبالإمكان إضافة الواجهتين: <code>DictionaryReader<T>‎</code> و <code>DictionaryWriter<T></code>‎ في البرنامج السابق واللتان تحتويان على تعريف استخدامٍ (implementation) آمنٍ لأنواع مختلفةٍ، ومن الممكن أيضًا إضافة تجريدات (abstractions) لنقل التحويل غير المتحقَّق منه (unchecked cast) من الشيفرة المستدعِية (calling cod) إلى تعريف الاستخدام (implementation)، ومن المفيد كذلك استخدام [[Kotlin/generics|التغيُّر المُعمَّم (generic variance)]].


أمّا في حالة الدوال المُعمَّمة (generic functions) فإنّ استخدام [[Kotlin/inline functions|متحولات الأنواع من النوع reified]] يجعل التحويل مثل: <code>arg as T</code> محقَّقًا (checked) ما لم يكن لنوع <code>arg</code> متحولات النوع الخاصّة به والتي ستُزال.
أمّا في حالة الدوال المُعمَّمة (generic functions) فإنّ استخدام [[Kotlin/inline functions|معاملات الأنواع من النوع reified]] يجعل التحويل مثل: <code>arg as T</code> محقَّقًا (checked) ما لم يكن لنوع <code>arg</code> متحولات النوع الخاصّة به والتي ستُزال.


وللتجاوز عن تحذير التحويل غير المتحقَّق منه تُضاف [[Kotlin/annotations|الحاشية (annontation)]] بالصيغة <code>‎@Suppress("UNCHECKED_CAST")‎</code> للتعليمة أو التصريح الذي يحدث فيه ذلك التحذير، كما في الشيفرة:<syntaxhighlight lang="kotlin">
وللتجاوز عن تحذير التحويل غير المتحقَّق منه يضاف [[Kotlin/annotations|التوصيف (annontation)]] بالصيغة <code>‎@Suppress("UNCHECKED_CAST")‎</code> للتعليمة أو التصريح الذي يحدث فيه ذلك التحذير، كما في الشيفرة:<syntaxhighlight lang="kotlin">
inline fun <reified T> List<*>.asListOfType(): List<T>? =
inline fun <reified T> List<*>.asListOfType(): List<T>? =
     if (all { it is T })
     if (all { it is T })
سطر 114: سطر 114:
         this as List<T> else
         this as List<T> else
         null
         null
</syntaxhighlight>إذ تحتفظ [[Kotlin/basic types|أنواع المصفوفات (array types)]] في بيئة JVM (مثل <code>Array<Foo></code>‎) بمعلوماتٍ عن النوع المُزال (erased) لعناصرها، ويُتحقَّق جزئيًا من عملية التحويل إلى نوع المصفوفة حيث ستبقى قابلية احتواء القيمة الفارغة (nullability) ومتحولات النوع الفعلي للعناصر مُزالةً (erasaed)، فسينجح مثلًا التحويل <code>foo as Array<List<String>?></code>‎  إن كانت <code>foo</code>  مصفوفةً تحمل أيًا من <code>List<*>‎</code> سواءً أكانت nullable أم لا.
</syntaxhighlight>إذ تحتفظ [[Kotlin/basic types|أنواع المصفوفات (array types)]] في بيئة JVM (مثل <code>Array<Foo></code>‎) بمعلوماتٍ عن النوع المُزال (erased) لعناصرها، ويُتحقَّق جزئيًا من عملية التحويل إلى نوع المصفوفة حيث ستبقى قابلية احتواء القيمة الفارغة (nullability) ووسائط النوع الفعلي للعناصر مُزالةً (erasaed)، فسينجح مثلًا التحويل <code>foo as Array<List<String>?></code>‎  إن كانت <code>foo</code>  مصفوفةً تحمل أيًا من <code>List<*>‎</code> سواءً أكانت nullable أم لا.


== مصادر<span> </span> ==
== مصادر<span> </span> ==

المراجعة الحالية بتاريخ 17:21، 4 يوليو 2018

المعاملين is و ‎!is

تدعم لغة Kotlin ميّزة التحقُّق من توافق الكائن مع أحد الأنواع أثناء التنفيذ، وذلك بالاعتماد على المُعامِل is أو صيغته المنفيّة ‎!is كما في الشيفرة:

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // !(obj is String) مكافئ للصيغة
    print("Not a String")
}
else {
    print(obj.length)
}

التحويلات الذكية (Smart Casts)

لا حاجة في كثيرٍ من الأحيان لجعل التحويل صريحًا (explicit) في لغة Kotlin لأنّ المترجم (compiler) يتتبَّع عمليات التحقُّق (عبر المعامل is) والتحويل الصريح للقيم الثابتة (immutable) ويُحوِّلها تلقائيًا وبشكلٍ آمنٍ عندما تدعو الحاجة لذلك، كما في الشيفرة:

fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x سيُحوّل المعامل
                        // String تلقائيًا إلى النوع
    }
}

حيث يُعدُّ التحويل "آمنًا" إن كان التحقُّق المنفيّ يؤدي إلى أمر الرجوع return مثل:

    if (x !is String) return
    print(x.length) // x سيُحوّل المعامل
                    // String تلقائيًا إلى النوع

أو في الجانب الأيمن من المعامِلين && أو || كما في الشيفرة:

    // سيُحوّل المتغير تلقائيًا إلى نوع السلسلة النصيّة 
    // لوقوعه على الجانب الأيمن من المعامل || في الشرط 
    if (x !is String || x.length == 0) return

    // سيُحوّل المتغير تلقائيًا إلى نوع السلسلة النصيّة 
    // لوقوعه على الجانب الأيمن من المعامل && في الشرط 
    if (x is String && x.length > 0) {
        print(x.length) // x is automatically cast to String
    }

وبالإمكان استخدام مثل هذه التحويلات الذكيّة في تعابير when وحلقات while أيضًا كما في الشيفرة:

when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

ولا تُجرَى التحويلات الذكيّة عندما يكون المترجِم (compiler) غير قادرٍ على ضمان عدم تغيُّر المتغيِّر بين عملية التحقُّق والاستخدام، وبتفصيلٍ أكثر؛ تُجرَى التحويلات الذكيّة في الحالات الآتية:

  • المتغيِّرات المحليّة (local) من النوع val: دائمًا باستثناء الخاصّيّات المُعمَّمة المحليّة (local delegated properties).
  • الخاصّيّات (properties) من النوع val: فقط إن كانت الخاصّيّات خاصةً (private) أو داخليةً (internal) أو أُجري التحقُّق منها في نفس الوحدة (module) التي تحتوي على تصريح الخاصّيّة، ولا تتمّ التحويلات في حالة الخاصّيّات المفتوحة (open) أو تلك التي يكون فيها getter مُخصَّص.
  • المتغيِّرات المحليّة من النوع var: فقط إن لم يتغيّر المتغيِّر ما بين التحقُّق والاستخدام ولم يخضغ لتعبير lambda يحوِّل من قيمته أو لم يكن خاصّيّةً مُعمَّمةً محليّةً.
  • الخاصّيّات من النوع var: أبدًا، وذلك لأنّ المتغيِّر قد يتغيّر في أيِّ لحظةٍ ممكنةٍ وعبر أيّ شيفرة أخرى.

معامل التحويل غير الآمن

سينتُج عن استخدام معامل التحويل استثناءٌ (exception) إن لم يكن التحويل متاحًا، وهذا ما ندعوه بالتحويل غير الآمن (unsafe) ويُعتمَد في Kotlin على المعامل as بالصيغة الداخليّة (infix) كما في الشيفرة:

val x: String = y as String

إذ لا يُمكن تحويل القيمة الفارغة null إلى النوع String لأنّه ليس نوعًا nullable، وبالتالي فإن كانت قيمة المتغيِّر y هي null سينتُج استثناء. ولتحقيق التماثل مع صيغة لغة Java يجب أن يكون النوع nullable في الجانب الأيمن من التحويل كما في الشيفرة:

val x: String? = y as String?

معامل التحويل الآمن (Nullable)

يُستخدَم معامل التحويل الآمن as?‎ (والذي يعيد القيمة null عند حدوث أيِّ خللٍ) لمنع حدوث الاستثناء (exception) السابق، مثل:

val x: String? = y as? String

وعلى الرغم من أنّ الجانب الأيمن من المعامِل as?‎ هو من النوع String (الذي لا يقبل null) فقد تكون نتيجة التحويل الإجماليّة null (أي أنّها nullable).

إزالة الأنواع (Type Erasure) والتحقُّق من الأنواع المُعمَّمة (Generic Type)

تضمن لغة Kotlin أمان الأنواع (type-safety) -بما فيها الأنواع المُعمَّمة (generics)- أثناء الترجمة (compilation) فقط، أما أثناء التنفيذ (runtime) فقد لا تحتوي بعض الكائنات من الأنواع المُعمَّمة على معلوماتٍ عن وسائط أنواعها الفعليّة، فيُزال النوع List<Foo>‎ مثلًا ليشمله النوع List<*>‎، إذ لا توجد طريقةٌ للتحقُّق من انتماء الكائن إلى نوعٍ مُعمَّمٍ (بوسائط الأنواع) أثناء التنفيذ.

وبالتالي فإن المُترجِم (compiler) يمنع عمليات التحقُّق بالمعامل is أثناء التنفيذ بسبب إزالة الأنواع (type erasure) مثل: ints is List<Int>‎ أو list is T ، ولكن من الممكن التحقُّق من الكائنات المخالفة للأنواع ذات الإسقاط الواسع (star-projected type) كما في الشيفرة:

if (something is List<*>) {
    something.forEach { println(it) } // `Any?` ستُجعَل العناصر من النوع
}

وبشكلٍ مماثلٍ؛ وبما أنه تمّ التحقُّق الستاتيكيّ لوسائط الأنواع (type arguments) للكائن (instance) أثناء الترجمة فيمكن كذلك التحقُّق بالمعامل is أو القيام بأيّ تحويلٍ يحتوي على الجزء غير المُعمَّم (non-generic part) من هذا النوع، حيث ستُحذف الأقواس <> كما في الشيفرة:

fun handleStrings(list: List<String>) {
    if (list is ArrayList) {
        // سيُحوَّل الكائن list
        // إلى النوع `ArrayList<String>`
    }
}

ويُسمَح باستخدام نفس صيغة التعامل مع وسائط النوع المحذوفة في حالة التحويلات التي لا تأخذ وسائط النوع بالحسبان، مثل: list as ArrayList. وفي حالة الدوال المباشرة (inline functions) بمعاملات نوعٍ reified (والمُضمَّنة [inlined] في كلّ موقع استدعاءٍ للدالة) فيُتاح التحقُّق من معاملات النوع بالشكل: arg is T ، أما إن كان arg كائنًا (instance) من النوع المُعمَّم ذاته فستُزال كذلك وسائط النوع فيه، مثل:

inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
    if (first !is A || second !is B) return null
    return first as A to second as B
}

val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)

val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // Breaks type safety!

التحويلات غير المتحقَّق منها (Unchecked Casts)

إنّ مفهوم إزالة الأنواع (type erasure) في Kotlin يمنع -كما ذكرنا آنفًا- عمليات التحقُّق من وسائط النوع الفعليّ لكائنات الأنواع المُعمَّمة (generic type instances) أثناء التنفيذ (runtime)، وقد لا ترتبط الأنواع المُعمَّمة فيما بينها بما يكفي المترجمَ لضمان أمان الأنواع، وعلى الرغم من ذلك تشتمل (imply) بعض البرامج عالية المستوى (high-level) على أمان الأنواع، مثل:

fun readDictionary(file: File): Map<String, *> = file.inputStream().use { 
    TODO("Read a mapping of strings to arbitrary elements.")
}
// عملية حفظ map
// بقيم صحيحة في ذلك الملف
val intsFile = File("ints.dictionary")

// تحذير: عملية تحويل غير مُتحقَّق منها 
// `Map<String, *>` to `Map<String, Int>`
val intsDictionary: Map<String, Int> = readDictionary(intsFile) as Map<String, Int>

إذ يُصدِر المترجم تحذيرًا عند التحويل في السطر الأخير من الشيفرة حيث لا يمكن التحقُّق منه كليَّا أثناء التنفيذ وليس من المؤكَّد أن تكون القيم في كائن map من النوع Int.

ولتجنب ذلك النوع من التحويلات تُعاد هيكلة البرنامج؛ فبالإمكان إضافة الواجهتين: DictionaryReader<T>‎ و DictionaryWriter<T>‎ في البرنامج السابق واللتان تحتويان على تعريف استخدامٍ (implementation) آمنٍ لأنواع مختلفةٍ، ومن الممكن أيضًا إضافة تجريدات (abstractions) لنقل التحويل غير المتحقَّق منه (unchecked cast) من الشيفرة المستدعِية (calling cod) إلى تعريف الاستخدام (implementation)، ومن المفيد كذلك استخدام التغيُّر المُعمَّم (generic variance).

أمّا في حالة الدوال المُعمَّمة (generic functions) فإنّ استخدام معاملات الأنواع من النوع reified يجعل التحويل مثل: arg as T محقَّقًا (checked) ما لم يكن لنوع arg متحولات النوع الخاصّة به والتي ستُزال.

وللتجاوز عن تحذير التحويل غير المتحقَّق منه يضاف التوصيف (annontation) بالصيغة ‎@Suppress("UNCHECKED_CAST")‎ للتعليمة أو التصريح الذي يحدث فيه ذلك التحذير، كما في الشيفرة:

inline fun <reified T> List<*>.asListOfType(): List<T>? =
    if (all { it is T })
        @Suppress("UNCHECKED_CAST")
        this as List<T> else
        null

إذ تحتفظ أنواع المصفوفات (array types) في بيئة JVM (مثل Array<Foo>‎) بمعلوماتٍ عن النوع المُزال (erased) لعناصرها، ويُتحقَّق جزئيًا من عملية التحويل إلى نوع المصفوفة حيث ستبقى قابلية احتواء القيمة الفارغة (nullability) ووسائط النوع الفعلي للعناصر مُزالةً (erasaed)، فسينجح مثلًا التحويل foo as Array<List<String>?>‎ إن كانت foo  مصفوفةً تحمل أيًا من List<*>‎ سواءً أكانت nullable أم لا.

مصادر