الفرق بين المراجعتين ل"Kotlin/typecasts"
ط (تعديل مصطلح متحول) |
|||
(3 مراجعات متوسطة بواسطة مستخدمين اثنين آخرين غير معروضة) | |||
سطر 1: | سطر 1: | ||
− | <noinclude>{{DISPLAYTITLE: | + | <noinclude>{{DISPLAYTITLE:التحقق من الأنواع (Type Check) والتحويل بينها (Casting) في لغة Kotlin}}</noinclude> |
== المعاملين <code>is</code> و <code>!is</code> == | == المعاملين <code>is</code> و <code>!is</code> == | ||
تدعم لغة Kotlin ميّزة التحقُّق من توافق الكائن مع أحد الأنواع أثناء التنفيذ، وذلك بالاعتماد على المُعامِل <code>is</code> أو صيغته المنفيّة <code>!is</code> كما في الشيفرة:<syntaxhighlight lang="kotlin"> | تدعم لغة Kotlin ميّزة التحقُّق من توافق الكائن مع أحد الأنواع أثناء التنفيذ، وذلك بالاعتماد على المُعامِل <code>is</code> أو صيغته المنفيّة <code>!is</code> كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
سطر 14: | سطر 14: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == التحويلات | + | == التحويلات الذكية (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)]]. |
* '''الخاصّيّات (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>:''' أبدًا، وذلك لأنّ | + | * '''الخاصّيّات من النوع <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، وبالتالي فإن كانت قيمة | + | </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) فقد لا تحتوي بعض الكائنات من الأنواع المُعمَّمة على معلوماتٍ عن | + | تضمن لغة 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>وبشكلٍ مماثلٍ؛ وبما أنه تمّ التحقُّق الستاتيكيّ | + | </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>ويُسمَح باستخدام نفس صيغة التعامل مع | + | </syntaxhighlight>ويُسمَح باستخدام نفس صيغة التعامل مع وسائط النوع المحذوفة في حالة التحويلات التي لا تأخذ وسائط النوع بالحسبان، مثل: <code>list as ArrayList</code>. |
− | وفي حالة الدوال | + | وفي حالة الدوال المباشرة (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 يمنع -كما ذكرنا آنفًا- عمليات التحقُّق من | + | إنّ مفهوم إزالة الأنواع (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| | + | أمّا في حالة الدوال المُعمَّمة (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"> |
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) | + | </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 أم لا.